|
|
@@ -51,7 +51,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
package psiphon
|
|
|
|
|
|
import (
|
|
|
- "container/list"
|
|
|
"crypto/tls"
|
|
|
"crypto/x509"
|
|
|
"errors"
|
|
|
@@ -64,11 +63,9 @@ import (
|
|
|
"os"
|
|
|
"reflect"
|
|
|
"sync"
|
|
|
- "sync/atomic"
|
|
|
"time"
|
|
|
|
|
|
"github.com/Psiphon-Inc/dns"
|
|
|
- "github.com/Psiphon-Inc/ratelimit"
|
|
|
)
|
|
|
|
|
|
const DNS_PORT = 53
|
|
|
@@ -227,92 +224,6 @@ func (conns *Conns) CloseAll() {
|
|
|
conns.conns = make(map[net.Conn]bool)
|
|
|
}
|
|
|
|
|
|
-// LRUConns is a concurrency-safe list of net.Conns ordered
|
|
|
-// by recent activity. Its purpose is to facilitate closing
|
|
|
-// the oldest connection in a set of connections.
|
|
|
-//
|
|
|
-// New connections added are referenced by a LRUConnsEntry,
|
|
|
-// which is used to Touch() active connections, which
|
|
|
-// promotes them to the front of the order and to Remove()
|
|
|
-// connections that are no longer LRU candidates.
|
|
|
-//
|
|
|
-// CloseOldest() will remove the oldest connection from the
|
|
|
-// list and call net.Conn.Close() on the connection.
|
|
|
-//
|
|
|
-// After an entry has been removed, LRUConnsEntry Touch()
|
|
|
-// and Remove() will have no effect.
|
|
|
-type LRUConns struct {
|
|
|
- mutex sync.Mutex
|
|
|
- list *list.List
|
|
|
-}
|
|
|
-
|
|
|
-// NewLRUConns initializes a new LRUConns.
|
|
|
-func NewLRUConns() *LRUConns {
|
|
|
- return &LRUConns{list: list.New()}
|
|
|
-}
|
|
|
-
|
|
|
-// Add inserts a net.Conn as the freshest connection
|
|
|
-// in a LRUConns and returns an LRUConnsEntry to be
|
|
|
-// used to freshen the connection or remove the connection
|
|
|
-// from the LRU list.
|
|
|
-func (conns *LRUConns) Add(conn net.Conn) *LRUConnsEntry {
|
|
|
- conns.mutex.Lock()
|
|
|
- defer conns.mutex.Unlock()
|
|
|
- return &LRUConnsEntry{
|
|
|
- lruConns: conns,
|
|
|
- element: conns.list.PushFront(conn),
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// CloseOldest closes the oldest connection in a
|
|
|
-// LRUConns. It calls net.Conn.Close() on the
|
|
|
-// connection.
|
|
|
-func (conns *LRUConns) CloseOldest() {
|
|
|
- conns.mutex.Lock()
|
|
|
- oldest := conns.list.Back()
|
|
|
- conn, ok := oldest.Value.(net.Conn)
|
|
|
- if oldest != nil {
|
|
|
- conns.list.Remove(oldest)
|
|
|
- }
|
|
|
- // Release mutex before closing conn
|
|
|
- conns.mutex.Unlock()
|
|
|
- if ok {
|
|
|
- conn.Close()
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// LRUConnsEntry is an entry in a LRUConns list.
|
|
|
-type LRUConnsEntry struct {
|
|
|
- lruConns *LRUConns
|
|
|
- element *list.Element
|
|
|
-}
|
|
|
-
|
|
|
-// Remove deletes the connection referenced by the
|
|
|
-// LRUConnsEntry from the associated LRUConns.
|
|
|
-// Has no effect if the entry was not initialized
|
|
|
-// or previously removed.
|
|
|
-func (entry *LRUConnsEntry) Remove() {
|
|
|
- if entry.lruConns == nil || entry.element == nil {
|
|
|
- return
|
|
|
- }
|
|
|
- entry.lruConns.mutex.Lock()
|
|
|
- defer entry.lruConns.mutex.Unlock()
|
|
|
- entry.lruConns.list.Remove(entry.element)
|
|
|
-}
|
|
|
-
|
|
|
-// Touch promotes the connection referenced by the
|
|
|
-// LRUConnsEntry to the front of the associated LRUConns.
|
|
|
-// Has no effect if the entry was not initialized
|
|
|
-// or previously removed.
|
|
|
-func (entry *LRUConnsEntry) Touch() {
|
|
|
- if entry.lruConns == nil || entry.element == nil {
|
|
|
- return
|
|
|
- }
|
|
|
- entry.lruConns.mutex.Lock()
|
|
|
- defer entry.lruConns.mutex.Unlock()
|
|
|
- entry.lruConns.list.MoveToFront(entry.element)
|
|
|
-}
|
|
|
-
|
|
|
// LocalProxyRelay sends to remoteConn bytes received from localConn,
|
|
|
// and sends to localConn bytes received from remoteConn.
|
|
|
func LocalProxyRelay(proxyType string, localConn, remoteConn net.Conn) {
|
|
|
@@ -737,185 +648,3 @@ func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
|
|
|
tc.SetKeepAlivePeriod(3 * time.Minute)
|
|
|
return tc, nil
|
|
|
}
|
|
|
-
|
|
|
-// ActivityMonitoredConn wraps a net.Conn, adding logic to deal with
|
|
|
-// events triggered by I/O activity.
|
|
|
-//
|
|
|
-// When an inactivity timeout is specified, the network I/O will
|
|
|
-// timeout after the specified period of read inactivity. Optionally,
|
|
|
-// ActivityMonitoredConn will also consider the connection active when
|
|
|
-// data is written to it.
|
|
|
-//
|
|
|
-// When a LRUConnsEntry is specified, then the LRU entry is promoted on
|
|
|
-// either a successful read or write.
|
|
|
-//
|
|
|
-type ActivityMonitoredConn struct {
|
|
|
- net.Conn
|
|
|
- inactivityTimeout time.Duration
|
|
|
- activeOnWrite bool
|
|
|
- startTime int64
|
|
|
- lastReadActivityTime int64
|
|
|
- lruEntry *LRUConnsEntry
|
|
|
-}
|
|
|
-
|
|
|
-func NewActivityMonitoredConn(
|
|
|
- conn net.Conn,
|
|
|
- inactivityTimeout time.Duration,
|
|
|
- activeOnWrite bool,
|
|
|
- lruEntry *LRUConnsEntry) (*ActivityMonitoredConn, error) {
|
|
|
-
|
|
|
- if inactivityTimeout > 0 {
|
|
|
- err := conn.SetDeadline(time.Now().Add(inactivityTimeout))
|
|
|
- if err != nil {
|
|
|
- return nil, ContextError(err)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- now := time.Now().UnixNano()
|
|
|
-
|
|
|
- return &ActivityMonitoredConn{
|
|
|
- Conn: conn,
|
|
|
- inactivityTimeout: inactivityTimeout,
|
|
|
- activeOnWrite: activeOnWrite,
|
|
|
- startTime: now,
|
|
|
- lastReadActivityTime: now,
|
|
|
- lruEntry: lruEntry,
|
|
|
- }, nil
|
|
|
-}
|
|
|
-
|
|
|
-// GetStartTime gets the time when the ActivityMonitoredConn was
|
|
|
-// initialized.
|
|
|
-func (conn *ActivityMonitoredConn) GetStartTime() time.Time {
|
|
|
- return time.Unix(0, conn.startTime)
|
|
|
-}
|
|
|
-
|
|
|
-// GetActiveDuration returns the time elapsed between the initialization
|
|
|
-// of the ActivityMonitoredConn and the last Read. Only reads are used
|
|
|
-// for this calculation since writes may succeed locally due to buffering.
|
|
|
-func (conn *ActivityMonitoredConn) GetActiveDuration() time.Duration {
|
|
|
- return time.Duration(atomic.LoadInt64(&conn.lastReadActivityTime) - conn.startTime)
|
|
|
-}
|
|
|
-
|
|
|
-func (conn *ActivityMonitoredConn) Read(buffer []byte) (int, error) {
|
|
|
- n, err := conn.Conn.Read(buffer)
|
|
|
- if err == nil {
|
|
|
-
|
|
|
- if conn.inactivityTimeout > 0 {
|
|
|
- err = conn.Conn.SetDeadline(time.Now().Add(conn.inactivityTimeout))
|
|
|
- if err != nil {
|
|
|
- return n, ContextError(err)
|
|
|
- }
|
|
|
- }
|
|
|
- if conn.lruEntry != nil {
|
|
|
- conn.lruEntry.Touch()
|
|
|
- }
|
|
|
-
|
|
|
- atomic.StoreInt64(&conn.lastReadActivityTime, time.Now().UnixNano())
|
|
|
-
|
|
|
- }
|
|
|
- // Note: no context error to preserve error type
|
|
|
- return n, err
|
|
|
-}
|
|
|
-
|
|
|
-func (conn *ActivityMonitoredConn) Write(buffer []byte) (int, error) {
|
|
|
- n, err := conn.Conn.Write(buffer)
|
|
|
- if err == nil && conn.activeOnWrite {
|
|
|
-
|
|
|
- if conn.inactivityTimeout > 0 {
|
|
|
- err = conn.Conn.SetDeadline(time.Now().Add(conn.inactivityTimeout))
|
|
|
- if err != nil {
|
|
|
- return n, ContextError(err)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if conn.lruEntry != nil {
|
|
|
- conn.lruEntry.Touch()
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
- // Note: no context error to preserve error type
|
|
|
- return n, err
|
|
|
-}
|
|
|
-
|
|
|
-// ThrottledConn wraps a net.Conn with read and write rate limiters.
|
|
|
-// Rates are specified as bytes per second. Optional unlimited byte
|
|
|
-// counts allow for a number of bytes to read or write before
|
|
|
-// applying rate limiting. Specify limit values of 0 to set no rate
|
|
|
-// limit (unlimited counts are ignored in this case).
|
|
|
-// The underlying rate limiter uses the token bucket algorithm to
|
|
|
-// calculate delay times for read and write operations.
|
|
|
-type ThrottledConn struct {
|
|
|
- net.Conn
|
|
|
- unlimitedReadBytes int64
|
|
|
- limitingReads int32
|
|
|
- limitedReader io.Reader
|
|
|
- unlimitedWriteBytes int64
|
|
|
- limitingWrites int32
|
|
|
- limitedWriter io.Writer
|
|
|
-}
|
|
|
-
|
|
|
-// NewThrottledConn initializes a new ThrottledConn.
|
|
|
-func NewThrottledConn(
|
|
|
- conn net.Conn,
|
|
|
- unlimitedReadBytes, limitReadBytesPerSecond,
|
|
|
- unlimitedWriteBytes, limitWriteBytesPerSecond int64) *ThrottledConn {
|
|
|
-
|
|
|
- // When no limit is specified, the rate limited reader/writer
|
|
|
- // is simply the base reader/writer.
|
|
|
-
|
|
|
- var reader io.Reader
|
|
|
- if limitReadBytesPerSecond == 0 {
|
|
|
- reader = conn
|
|
|
- } else {
|
|
|
- reader = ratelimit.Reader(conn,
|
|
|
- ratelimit.NewBucketWithRate(
|
|
|
- float64(limitReadBytesPerSecond), limitReadBytesPerSecond))
|
|
|
- }
|
|
|
-
|
|
|
- var writer io.Writer
|
|
|
- if limitWriteBytesPerSecond == 0 {
|
|
|
- writer = conn
|
|
|
- } else {
|
|
|
- writer = ratelimit.Writer(conn,
|
|
|
- ratelimit.NewBucketWithRate(
|
|
|
- float64(limitWriteBytesPerSecond), limitWriteBytesPerSecond))
|
|
|
- }
|
|
|
-
|
|
|
- return &ThrottledConn{
|
|
|
- Conn: conn,
|
|
|
- unlimitedReadBytes: unlimitedReadBytes,
|
|
|
- limitingReads: 0,
|
|
|
- limitedReader: reader,
|
|
|
- unlimitedWriteBytes: unlimitedWriteBytes,
|
|
|
- limitingWrites: 0,
|
|
|
- limitedWriter: writer,
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func (conn *ThrottledConn) Read(buffer []byte) (int, error) {
|
|
|
-
|
|
|
- // Use the base reader until the unlimited count is exhausted.
|
|
|
- if atomic.LoadInt32(&conn.limitingReads) == 0 {
|
|
|
- if atomic.AddInt64(&conn.unlimitedReadBytes, -int64(len(buffer))) <= 0 {
|
|
|
- atomic.StoreInt32(&conn.limitingReads, 1)
|
|
|
- } else {
|
|
|
- return conn.Read(buffer)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return conn.limitedReader.Read(buffer)
|
|
|
-}
|
|
|
-
|
|
|
-func (conn *ThrottledConn) Write(buffer []byte) (int, error) {
|
|
|
-
|
|
|
- // Use the base writer until the unlimited count is exhausted.
|
|
|
- if atomic.LoadInt32(&conn.limitingWrites) == 0 {
|
|
|
- if atomic.AddInt64(&conn.unlimitedWriteBytes, -int64(len(buffer))) <= 0 {
|
|
|
- atomic.StoreInt32(&conn.limitingWrites, 1)
|
|
|
- } else {
|
|
|
- return conn.Write(buffer)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return conn.limitedWriter.Write(buffer)
|
|
|
-}
|