|
@@ -26,10 +26,7 @@ import (
|
|
|
"net/http"
|
|
"net/http"
|
|
|
"strconv"
|
|
"strconv"
|
|
|
"sync"
|
|
"sync"
|
|
|
- "sync/atomic"
|
|
|
|
|
- "time"
|
|
|
|
|
|
|
|
|
|
- "github.com/Psiphon-Labs/goarista/monotime"
|
|
|
|
|
"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
|
|
"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
|
|
|
"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/prng"
|
|
"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/prng"
|
|
|
"github.com/miekg/dns"
|
|
"github.com/miekg/dns"
|
|
@@ -261,412 +258,6 @@ func (entry *LRUConnsEntry) Touch() {
|
|
|
entry.lruConns.list.MoveToFront(entry.element)
|
|
entry.lruConns.list.MoveToFront(entry.element)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// ActivityMonitoredConn wraps a net.Conn, adding logic to deal with events
|
|
|
|
|
-// triggered by I/O activity.
|
|
|
|
|
-//
|
|
|
|
|
-// ActivityMonitoredConn uses lock-free concurrency synronization, avoiding an
|
|
|
|
|
-// additional mutex resource, making it suitable for wrapping many net.Conns
|
|
|
|
|
-// (e.g, each Psiphon port forward).
|
|
|
|
|
-//
|
|
|
|
|
-// When an inactivity timeout is specified, the network I/O will timeout after
|
|
|
|
|
-// the specified period of read inactivity. Optionally, for the purpose of
|
|
|
|
|
-// inactivity only, 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.
|
|
|
|
|
-//
|
|
|
|
|
-// When an ActivityUpdater is set, then its UpdateActivity method is called on
|
|
|
|
|
-// each read and write with the number of bytes transferred. The
|
|
|
|
|
-// durationNanoseconds, which is the time since the last read, is reported
|
|
|
|
|
-// only on reads.
|
|
|
|
|
-type ActivityMonitoredConn struct {
|
|
|
|
|
- // Note: 64-bit ints used with atomic operations are placed
|
|
|
|
|
- // at the start of struct to ensure 64-bit alignment.
|
|
|
|
|
- // (https://golang.org/pkg/sync/atomic/#pkg-note-BUG)
|
|
|
|
|
- monotonicStartTime int64
|
|
|
|
|
- lastReadActivityTime int64
|
|
|
|
|
- realStartTime time.Time
|
|
|
|
|
- net.Conn
|
|
|
|
|
- inactivityTimeout time.Duration
|
|
|
|
|
- activeOnWrite bool
|
|
|
|
|
- activityUpdater ActivityUpdater
|
|
|
|
|
- lruEntry *LRUConnsEntry
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// ActivityUpdater defines an interface for receiving updates for
|
|
|
|
|
-// ActivityMonitoredConn activity. Values passed to UpdateProgress are bytes
|
|
|
|
|
-// transferred and conn duration since the previous UpdateProgress.
|
|
|
|
|
-type ActivityUpdater interface {
|
|
|
|
|
- UpdateProgress(bytesRead, bytesWritten int64, durationNanoseconds int64)
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// NewActivityMonitoredConn creates a new ActivityMonitoredConn.
|
|
|
|
|
-func NewActivityMonitoredConn(
|
|
|
|
|
- conn net.Conn,
|
|
|
|
|
- inactivityTimeout time.Duration,
|
|
|
|
|
- activeOnWrite bool,
|
|
|
|
|
- activityUpdater ActivityUpdater,
|
|
|
|
|
- lruEntry *LRUConnsEntry) (*ActivityMonitoredConn, error) {
|
|
|
|
|
-
|
|
|
|
|
- if inactivityTimeout > 0 {
|
|
|
|
|
- err := conn.SetDeadline(time.Now().Add(inactivityTimeout))
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return nil, errors.Trace(err)
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // The "monotime" package is still used here as its time value is an int64,
|
|
|
|
|
- // which is compatible with atomic operations.
|
|
|
|
|
-
|
|
|
|
|
- now := int64(monotime.Now())
|
|
|
|
|
-
|
|
|
|
|
- return &ActivityMonitoredConn{
|
|
|
|
|
- Conn: conn,
|
|
|
|
|
- inactivityTimeout: inactivityTimeout,
|
|
|
|
|
- activeOnWrite: activeOnWrite,
|
|
|
|
|
- realStartTime: time.Now(),
|
|
|
|
|
- monotonicStartTime: now,
|
|
|
|
|
- lastReadActivityTime: now,
|
|
|
|
|
- activityUpdater: activityUpdater,
|
|
|
|
|
- lruEntry: lruEntry,
|
|
|
|
|
- }, nil
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// GetStartTime gets the time when the ActivityMonitoredConn was initialized.
|
|
|
|
|
-// Reported time is UTC.
|
|
|
|
|
-func (conn *ActivityMonitoredConn) GetStartTime() time.Time {
|
|
|
|
|
- return conn.realStartTime.UTC()
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 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.monotonicStartTime)
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-func (conn *ActivityMonitoredConn) Read(buffer []byte) (int, error) {
|
|
|
|
|
- n, err := conn.Conn.Read(buffer)
|
|
|
|
|
- if n > 0 {
|
|
|
|
|
-
|
|
|
|
|
- if conn.inactivityTimeout > 0 {
|
|
|
|
|
- err = conn.Conn.SetDeadline(time.Now().Add(conn.inactivityTimeout))
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return n, errors.Trace(err)
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- lastReadActivityTime := atomic.LoadInt64(&conn.lastReadActivityTime)
|
|
|
|
|
- readActivityTime := int64(monotime.Now())
|
|
|
|
|
-
|
|
|
|
|
- atomic.StoreInt64(&conn.lastReadActivityTime, readActivityTime)
|
|
|
|
|
-
|
|
|
|
|
- if conn.activityUpdater != nil {
|
|
|
|
|
- conn.activityUpdater.UpdateProgress(
|
|
|
|
|
- int64(n), 0, readActivityTime-lastReadActivityTime)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if conn.lruEntry != nil {
|
|
|
|
|
- conn.lruEntry.Touch()
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- // 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 n > 0 && conn.activeOnWrite {
|
|
|
|
|
-
|
|
|
|
|
- if conn.inactivityTimeout > 0 {
|
|
|
|
|
- err = conn.Conn.SetDeadline(time.Now().Add(conn.inactivityTimeout))
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return n, errors.Trace(err)
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if conn.activityUpdater != nil {
|
|
|
|
|
- conn.activityUpdater.UpdateProgress(0, int64(n), 0)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if conn.lruEntry != nil {
|
|
|
|
|
- conn.lruEntry.Touch()
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- }
|
|
|
|
|
- // Note: no context error to preserve error type
|
|
|
|
|
- return n, err
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// IsClosed implements the Closer iterface. The return value indicates whether
|
|
|
|
|
-// the underlying conn has been closed.
|
|
|
|
|
-func (conn *ActivityMonitoredConn) IsClosed() bool {
|
|
|
|
|
- closer, ok := conn.Conn.(Closer)
|
|
|
|
|
- if !ok {
|
|
|
|
|
- return false
|
|
|
|
|
- }
|
|
|
|
|
- return closer.IsClosed()
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// BurstMonitoredConn wraps a net.Conn and monitors for data transfer bursts.
|
|
|
|
|
-// Upstream (read) and downstream (write) bursts are tracked independently.
|
|
|
|
|
-//
|
|
|
|
|
-// A burst is defined as a transfer of at least "threshold" bytes, across
|
|
|
|
|
-// multiple I/O operations where the delay between operations does not exceed
|
|
|
|
|
-// "deadline". Both a non-zero deadline and theshold must be set to enable
|
|
|
|
|
-// monitoring. Four bursts are reported: the first, the last, the min (by
|
|
|
|
|
-// rate) and max.
|
|
|
|
|
-//
|
|
|
|
|
-// The reported rates will be more accurate for larger data transfers,
|
|
|
|
|
-// especially for higher transfer rates. Tune the deadline/threshold as
|
|
|
|
|
-// required. The threshold should be set to account for buffering (e.g, the
|
|
|
|
|
-// local host socket send/receive buffer) but this is not enforced by
|
|
|
|
|
-// BurstMonitoredConn.
|
|
|
|
|
-//
|
|
|
|
|
-// Close must be called to complete any outstanding bursts. For complete
|
|
|
|
|
-// results, call GetMetrics only after Close is called.
|
|
|
|
|
-//
|
|
|
|
|
-// Overhead: BurstMonitoredConn adds mutexes but does not use timers.
|
|
|
|
|
-type BurstMonitoredConn struct {
|
|
|
|
|
- net.Conn
|
|
|
|
|
- upstreamDeadline time.Duration
|
|
|
|
|
- upstreamThresholdBytes int64
|
|
|
|
|
- downstreamDeadline time.Duration
|
|
|
|
|
- downstreamThresholdBytes int64
|
|
|
|
|
-
|
|
|
|
|
- readMutex sync.Mutex
|
|
|
|
|
- currentUpstreamBurst burst
|
|
|
|
|
- upstreamBursts burstHistory
|
|
|
|
|
-
|
|
|
|
|
- writeMutex sync.Mutex
|
|
|
|
|
- currentDownstreamBurst burst
|
|
|
|
|
- downstreamBursts burstHistory
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// NewBurstMonitoredConn creates a new BurstMonitoredConn.
|
|
|
|
|
-func NewBurstMonitoredConn(
|
|
|
|
|
- conn net.Conn,
|
|
|
|
|
- upstreamDeadline time.Duration,
|
|
|
|
|
- upstreamThresholdBytes int64,
|
|
|
|
|
- downstreamDeadline time.Duration,
|
|
|
|
|
- downstreamThresholdBytes int64) *BurstMonitoredConn {
|
|
|
|
|
-
|
|
|
|
|
- return &BurstMonitoredConn{
|
|
|
|
|
- Conn: conn,
|
|
|
|
|
- upstreamDeadline: upstreamDeadline,
|
|
|
|
|
- upstreamThresholdBytes: upstreamThresholdBytes,
|
|
|
|
|
- downstreamDeadline: downstreamDeadline,
|
|
|
|
|
- downstreamThresholdBytes: downstreamThresholdBytes,
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-type burst struct {
|
|
|
|
|
- startTime time.Time
|
|
|
|
|
- lastByteTime time.Time
|
|
|
|
|
- bytes int64
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-func (b *burst) isZero() bool {
|
|
|
|
|
- return b.startTime.IsZero()
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-func (b *burst) offset(baseTime time.Time) time.Duration {
|
|
|
|
|
- offset := b.startTime.Sub(baseTime)
|
|
|
|
|
- if offset <= 0 {
|
|
|
|
|
- return 0
|
|
|
|
|
- }
|
|
|
|
|
- return offset
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-func (b *burst) duration() time.Duration {
|
|
|
|
|
- duration := b.lastByteTime.Sub(b.startTime)
|
|
|
|
|
- if duration <= 0 {
|
|
|
|
|
- return 0
|
|
|
|
|
- }
|
|
|
|
|
- return duration
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-func (b *burst) rate() int64 {
|
|
|
|
|
- return int64(
|
|
|
|
|
- (float64(b.bytes) * float64(time.Second)) /
|
|
|
|
|
- float64(b.duration()))
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-type burstHistory struct {
|
|
|
|
|
- first burst
|
|
|
|
|
- last burst
|
|
|
|
|
- min burst
|
|
|
|
|
- max burst
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-func (conn *BurstMonitoredConn) Read(buffer []byte) (int, error) {
|
|
|
|
|
-
|
|
|
|
|
- start := time.Now()
|
|
|
|
|
- n, err := conn.Conn.Read(buffer)
|
|
|
|
|
- end := time.Now()
|
|
|
|
|
-
|
|
|
|
|
- if n > 0 &&
|
|
|
|
|
- conn.upstreamDeadline > 0 && conn.upstreamThresholdBytes > 0 {
|
|
|
|
|
-
|
|
|
|
|
- conn.readMutex.Lock()
|
|
|
|
|
- conn.updateBurst(
|
|
|
|
|
- start,
|
|
|
|
|
- end,
|
|
|
|
|
- int64(n),
|
|
|
|
|
- conn.upstreamDeadline,
|
|
|
|
|
- conn.upstreamThresholdBytes,
|
|
|
|
|
- &conn.currentUpstreamBurst,
|
|
|
|
|
- &conn.upstreamBursts)
|
|
|
|
|
- conn.readMutex.Unlock()
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Note: no context error to preserve error type
|
|
|
|
|
- return n, err
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-func (conn *BurstMonitoredConn) Write(buffer []byte) (int, error) {
|
|
|
|
|
-
|
|
|
|
|
- start := time.Now()
|
|
|
|
|
- n, err := conn.Conn.Write(buffer)
|
|
|
|
|
- end := time.Now()
|
|
|
|
|
-
|
|
|
|
|
- if n > 0 &&
|
|
|
|
|
- conn.downstreamDeadline > 0 && conn.downstreamThresholdBytes > 0 {
|
|
|
|
|
-
|
|
|
|
|
- conn.writeMutex.Lock()
|
|
|
|
|
- conn.updateBurst(
|
|
|
|
|
- start,
|
|
|
|
|
- end,
|
|
|
|
|
- int64(n),
|
|
|
|
|
- conn.downstreamDeadline,
|
|
|
|
|
- conn.downstreamThresholdBytes,
|
|
|
|
|
- &conn.currentDownstreamBurst,
|
|
|
|
|
- &conn.downstreamBursts)
|
|
|
|
|
- conn.writeMutex.Unlock()
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Note: no context error to preserve error type
|
|
|
|
|
- return n, err
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-func (conn *BurstMonitoredConn) Close() error {
|
|
|
|
|
- err := conn.Conn.Close()
|
|
|
|
|
-
|
|
|
|
|
- conn.readMutex.Lock()
|
|
|
|
|
- conn.endBurst(
|
|
|
|
|
- conn.upstreamThresholdBytes,
|
|
|
|
|
- &conn.currentUpstreamBurst,
|
|
|
|
|
- &conn.upstreamBursts)
|
|
|
|
|
- conn.readMutex.Unlock()
|
|
|
|
|
-
|
|
|
|
|
- conn.writeMutex.Lock()
|
|
|
|
|
- conn.endBurst(
|
|
|
|
|
- conn.downstreamThresholdBytes,
|
|
|
|
|
- &conn.currentDownstreamBurst,
|
|
|
|
|
- &conn.downstreamBursts)
|
|
|
|
|
- conn.writeMutex.Unlock()
|
|
|
|
|
-
|
|
|
|
|
- // Note: no context error to preserve error type
|
|
|
|
|
- return err
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// GetMetrics returns log fields with burst metrics for the first, last, min
|
|
|
|
|
-// (by rate), and max bursts for this conn. Time/duration values are reported
|
|
|
|
|
-// in milliseconds.
|
|
|
|
|
-func (conn *BurstMonitoredConn) GetMetrics(baseTime time.Time) LogFields {
|
|
|
|
|
- logFields := make(LogFields)
|
|
|
|
|
-
|
|
|
|
|
- addFields := func(prefix string, burst *burst) {
|
|
|
|
|
- if burst.isZero() {
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
- logFields[prefix+"offset"] = int64(burst.offset(baseTime) / time.Millisecond)
|
|
|
|
|
- logFields[prefix+"duration"] = int64(burst.duration() / time.Millisecond)
|
|
|
|
|
- logFields[prefix+"bytes"] = burst.bytes
|
|
|
|
|
- logFields[prefix+"rate"] = burst.rate()
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- addHistory := func(prefix string, history *burstHistory) {
|
|
|
|
|
- addFields(prefix+"first_", &history.first)
|
|
|
|
|
- addFields(prefix+"last_", &history.last)
|
|
|
|
|
- addFields(prefix+"min_", &history.min)
|
|
|
|
|
- addFields(prefix+"max_", &history.max)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- addHistory("burst_upstream_", &conn.upstreamBursts)
|
|
|
|
|
- addHistory("burst_downstream_", &conn.downstreamBursts)
|
|
|
|
|
-
|
|
|
|
|
- return logFields
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-func (conn *BurstMonitoredConn) updateBurst(
|
|
|
|
|
- operationStart time.Time,
|
|
|
|
|
- operationEnd time.Time,
|
|
|
|
|
- operationBytes int64,
|
|
|
|
|
- deadline time.Duration,
|
|
|
|
|
- thresholdBytes int64,
|
|
|
|
|
- currentBurst *burst,
|
|
|
|
|
- history *burstHistory) {
|
|
|
|
|
-
|
|
|
|
|
- // Assumes the associated mutex is locked.
|
|
|
|
|
-
|
|
|
|
|
- if currentBurst.isZero() {
|
|
|
|
|
- currentBurst.startTime = operationStart
|
|
|
|
|
- currentBurst.lastByteTime = operationEnd
|
|
|
|
|
- currentBurst.bytes = operationBytes
|
|
|
|
|
-
|
|
|
|
|
- } else {
|
|
|
|
|
-
|
|
|
|
|
- if operationStart.Sub(currentBurst.lastByteTime) >
|
|
|
|
|
- deadline {
|
|
|
|
|
-
|
|
|
|
|
- conn.endBurst(thresholdBytes, currentBurst, history)
|
|
|
|
|
- currentBurst.startTime = operationStart
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- currentBurst.lastByteTime = operationEnd
|
|
|
|
|
- currentBurst.bytes += operationBytes
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-func (conn *BurstMonitoredConn) endBurst(
|
|
|
|
|
- thresholdBytes int64,
|
|
|
|
|
- currentBurst *burst,
|
|
|
|
|
- history *burstHistory) {
|
|
|
|
|
-
|
|
|
|
|
- // Assumes the associated mutex is locked.
|
|
|
|
|
-
|
|
|
|
|
- if currentBurst.isZero() {
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- burst := *currentBurst
|
|
|
|
|
-
|
|
|
|
|
- currentBurst.startTime = time.Time{}
|
|
|
|
|
- currentBurst.lastByteTime = time.Time{}
|
|
|
|
|
- currentBurst.bytes = 0
|
|
|
|
|
-
|
|
|
|
|
- if burst.bytes < thresholdBytes {
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if history.first.isZero() {
|
|
|
|
|
- history.first = burst
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- history.last = burst
|
|
|
|
|
-
|
|
|
|
|
- if history.min.isZero() || history.min.rate() > burst.rate() {
|
|
|
|
|
- history.min = burst
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if history.max.isZero() || history.max.rate() < burst.rate() {
|
|
|
|
|
- history.max = burst
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
// IsBogon checks if the specified IP is a bogon (loopback, private addresses,
|
|
// IsBogon checks if the specified IP is a bogon (loopback, private addresses,
|
|
|
// link-local addresses, etc.)
|
|
// link-local addresses, etc.)
|
|
|
func IsBogon(IP net.IP) bool {
|
|
func IsBogon(IP net.IP) bool {
|