|
|
|
|
@ -16,6 +16,7 @@ import ( |
|
|
|
|
"time" |
|
|
|
|
|
|
|
|
|
"golang.org/x/crypto/nacl/box" |
|
|
|
|
"golang.org/x/time/rate" |
|
|
|
|
"tailscale.com/types/key" |
|
|
|
|
"tailscale.com/types/logger" |
|
|
|
|
) |
|
|
|
|
@ -32,8 +33,9 @@ type Client struct { |
|
|
|
|
canAckPings bool |
|
|
|
|
isProber bool |
|
|
|
|
|
|
|
|
|
wmu sync.Mutex // hold while writing to bw
|
|
|
|
|
bw *bufio.Writer |
|
|
|
|
wmu sync.Mutex // hold while writing to bw
|
|
|
|
|
bw *bufio.Writer |
|
|
|
|
rate *rate.Limiter // if non-nil, rate limiter to use
|
|
|
|
|
|
|
|
|
|
// Owned by Recv:
|
|
|
|
|
peeked int // bytes to discard on next Recv
|
|
|
|
|
@ -217,7 +219,12 @@ func (c *Client) send(dstKey key.Public, pkt []byte) (ret error) { |
|
|
|
|
|
|
|
|
|
c.wmu.Lock() |
|
|
|
|
defer c.wmu.Unlock() |
|
|
|
|
|
|
|
|
|
if c.rate != nil { |
|
|
|
|
pktLen := frameHeaderLen + len(dstKey) + len(pkt) |
|
|
|
|
if !c.rate.AllowN(time.Now(), pktLen) { |
|
|
|
|
return nil // drop
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if err := writeFrameHeader(c.bw, frameSendPacket, uint32(len(dstKey)+len(pkt))); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
@ -353,7 +360,22 @@ type PeerPresentMessage key.Public |
|
|
|
|
func (PeerPresentMessage) msg() {} |
|
|
|
|
|
|
|
|
|
// ServerInfoMessage is sent by the server upon first connect.
|
|
|
|
|
type ServerInfoMessage struct{} |
|
|
|
|
type ServerInfoMessage struct { |
|
|
|
|
// TokenBucketBytesPerSecond is how many bytes per second the
|
|
|
|
|
// server says it will accept, including all framing bytes.
|
|
|
|
|
//
|
|
|
|
|
// Zero means unspecified. There might be a limit, but the
|
|
|
|
|
// client need not try to respect it.
|
|
|
|
|
TokenBucketBytesPerSecond int |
|
|
|
|
|
|
|
|
|
// TokenBucketBytesBurst is how many bytes the server will
|
|
|
|
|
// allow to burst, temporarily violating
|
|
|
|
|
// TokenBucketBytesPerSecond.
|
|
|
|
|
//
|
|
|
|
|
// Zero means unspecified. There might be a limit, but the
|
|
|
|
|
// client need not try to respect it.
|
|
|
|
|
TokenBucketBytesBurst int |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (ServerInfoMessage) msg() {} |
|
|
|
|
|
|
|
|
|
@ -475,12 +497,16 @@ func (c *Client) recvTimeout(timeout time.Duration) (m ReceivedMessage, err erro |
|
|
|
|
// needing to wait an RTT to discover the version at startup.
|
|
|
|
|
// We'd prefer to give the connection to the client (magicsock)
|
|
|
|
|
// to start writing as soon as possible.
|
|
|
|
|
_, err := c.parseServerInfo(b) |
|
|
|
|
si, err := c.parseServerInfo(b) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, fmt.Errorf("invalid server info frame: %v", err) |
|
|
|
|
} |
|
|
|
|
// TODO: add the results of parseServerInfo to ServerInfoMessage if we ever need it.
|
|
|
|
|
return ServerInfoMessage{}, nil |
|
|
|
|
sm := ServerInfoMessage{ |
|
|
|
|
TokenBucketBytesPerSecond: si.TokenBucketBytesPerSecond, |
|
|
|
|
TokenBucketBytesBurst: si.TokenBucketBytesBurst, |
|
|
|
|
} |
|
|
|
|
c.setSendRateLimiter(sm) |
|
|
|
|
return sm, nil |
|
|
|
|
case frameKeepAlive: |
|
|
|
|
// A one-way keep-alive message that doesn't require an acknowledgement.
|
|
|
|
|
// This predated framePing/framePong.
|
|
|
|
|
@ -537,3 +563,16 @@ func (c *Client) recvTimeout(timeout time.Duration) (m ReceivedMessage, err erro |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (c *Client) setSendRateLimiter(sm ServerInfoMessage) { |
|
|
|
|
c.wmu.Lock() |
|
|
|
|
defer c.wmu.Unlock() |
|
|
|
|
|
|
|
|
|
if sm.TokenBucketBytesPerSecond == 0 { |
|
|
|
|
c.rate = nil |
|
|
|
|
} else { |
|
|
|
|
c.rate = rate.NewLimiter( |
|
|
|
|
rate.Limit(sm.TokenBucketBytesPerSecond), |
|
|
|
|
sm.TokenBucketBytesBurst) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|