mirror of
https://github.com/PeernetOfficial/core.git
synced 2026-07-17 02:47:51 +01:00
560 lines
17 KiB
Go
560 lines
17 KiB
Go
package udt
|
|
|
|
import (
|
|
"container/heap"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/PeernetOfficial/core/udt/packet"
|
|
)
|
|
|
|
type sendState int
|
|
|
|
const (
|
|
sendStateIdle sendState = iota // not waiting for anything, can send immediately
|
|
sendStateSending // recently sent something, waiting for SND before sending more
|
|
sendStateWaiting // destination is full, waiting for them to process something and come back
|
|
sendStateProcessDrop // immediately re-process any drop list requests
|
|
)
|
|
|
|
const (
|
|
minEXPinterval time.Duration = 300 * time.Millisecond
|
|
)
|
|
|
|
type udtSocketSend struct {
|
|
// channels
|
|
sockClosed <-chan struct{} // closed when socket is closed
|
|
sendEvent <-chan recvPktEvent // sender: ingest the specified packet. Sender is readPacket, receiver is goSendEvent
|
|
messageOut <-chan sendMessage // outbound messages. Sender is client caller (Write), Receiver is goSendEvent. Closed when socket is closed
|
|
sendPacket chan<- packet.Packet // send a packet out on the wire
|
|
shutdownEvent chan<- shutdownMessage // channel signals the connection to be shutdown
|
|
socket *udtSocket
|
|
|
|
sendState sendState // current sender state
|
|
sendPktPend sendPacketHeap // list of packets that have been sent but not yet acknoledged
|
|
sendPktSeq packet.PacketID // the current packet sequence number
|
|
msgPartialSend *sendMessage // when a message can only partially fit in a socket, this is the remainder
|
|
msgSeq uint32 // the current message sequence number
|
|
expCount uint // number of continuous EXP timeouts.
|
|
lastRecvTime time.Time // the last time we've heard something from the remote system
|
|
recvAckSeq packet.PacketID // largest packetID we've received an ACK from
|
|
sentAck2 uint32 // largest ACK2 packet we've sent
|
|
sendLossList packetIDHeap // loss list
|
|
sndPeriod atomicDuration // (set by congestion control) delay between sending packets
|
|
rtoPeriod atomicDuration // (set by congestion control) override of EXP timer calculations
|
|
congestWindow atomicUint32 // (set by congestion control) size of the current congestion window (in packets)
|
|
flowWindowSize uint // negotiated maximum number of unacknowledged packets (in packets)
|
|
|
|
// timers
|
|
sndEvent <-chan time.Time // if a packet is recently sent, this timer fires when SND completes
|
|
ack2SentEvent <-chan time.Time // if an ACK2 packet has recently sent, wait SYN before sending another one
|
|
expTimerEvent <-chan time.Time // Fires when we haven't heard from the peer in a while
|
|
}
|
|
|
|
func newUdtSocketSend(s *udtSocket) *udtSocketSend {
|
|
ss := &udtSocketSend{
|
|
socket: s,
|
|
expCount: 1,
|
|
sendPktSeq: s.initPktSeq,
|
|
sockClosed: s.sockClosed,
|
|
sendEvent: s.sendEvent,
|
|
messageOut: s.messageOut,
|
|
congestWindow: atomicUint32{val: 16},
|
|
flowWindowSize: s.maxFlowWinSize,
|
|
sendPacket: s.sendPacket,
|
|
shutdownEvent: s.shutdownEvent,
|
|
}
|
|
ss.resetEXP(s.created)
|
|
go ss.goSendEvent()
|
|
return ss
|
|
}
|
|
|
|
func (s *udtSocketSend) configureHandshake(p *packet.HandshakePacket, resetSeq bool) {
|
|
if resetSeq {
|
|
s.recvAckSeq = p.InitPktSeq
|
|
s.sendPktSeq = p.InitPktSeq
|
|
}
|
|
s.flowWindowSize = uint(p.MaxFlowWinSize)
|
|
}
|
|
|
|
func (s *udtSocketSend) SetPacketSendPeriod(snd time.Duration) {
|
|
// check to see if we have a bandwidth limit here
|
|
maxBandwidth := s.socket.Config.MaxBandwidth
|
|
if maxBandwidth > 0 {
|
|
minSP := time.Second / time.Duration(float64(maxBandwidth)/float64(s.socket.maxPacketSize))
|
|
if snd < minSP {
|
|
snd = minSP
|
|
}
|
|
}
|
|
|
|
s.sndPeriod.set(snd)
|
|
}
|
|
|
|
func (s *udtSocketSend) goSendEvent() {
|
|
sendEvent := s.sendEvent
|
|
messageOut := s.messageOut
|
|
sockClosed := s.sockClosed
|
|
for {
|
|
thisMsgChan := messageOut
|
|
|
|
switch s.sendState {
|
|
case sendStateIdle: // not waiting for anything, can send immediately
|
|
if s.msgPartialSend != nil { // we have a partial message waiting, try to send more of it now
|
|
s.processDataMsg(false, messageOut)
|
|
continue
|
|
}
|
|
case sendStateProcessDrop: // immediately re-process any drop list requests
|
|
s.sendState = s.reevalSendState() // try to reconstruct what our state should be if it wasn't sendStateProcessDrop
|
|
if !s.processSendLoss() || s.sendPktSeq.Seq%16 == 0 {
|
|
s.processSendExpire()
|
|
}
|
|
continue
|
|
default:
|
|
thisMsgChan = nil
|
|
}
|
|
|
|
select {
|
|
case msg, ok := <-thisMsgChan: // nil if we can't process outgoing messages right now
|
|
if !ok {
|
|
s.sendPacket <- &packet.ShutdownPacket{}
|
|
s.shutdownEvent <- shutdownMessage{sockState: sockStateClosed, permitLinger: !s.socket.isServer}
|
|
return
|
|
}
|
|
s.msgPartialSend = &msg
|
|
s.processDataMsg(true, messageOut)
|
|
case evt, ok := <-sendEvent:
|
|
if !ok {
|
|
return
|
|
}
|
|
s.expCount = 1
|
|
s.resetEXP(evt.now)
|
|
switch sp := evt.pkt.(type) {
|
|
case *packet.AckPacket:
|
|
s.ingestAck(sp, evt.now)
|
|
case *packet.LightAckPacket:
|
|
s.ingestLightAck(sp, evt.now)
|
|
case *packet.NakPacket:
|
|
s.ingestNak(sp, evt.now)
|
|
case *packet.CongestionPacket:
|
|
s.ingestCongestion(sp, evt.now)
|
|
}
|
|
s.sendState = s.reevalSendState()
|
|
case _, _ = <-sockClosed:
|
|
return
|
|
case <-s.ack2SentEvent: // ACK2 unlocked
|
|
s.ack2SentEvent = nil
|
|
case now := <-s.expTimerEvent: // EXP event
|
|
s.expEvent(now)
|
|
case <-s.sndEvent: // SND event
|
|
s.sndEvent = nil
|
|
if s.sendState == sendStateSending {
|
|
s.sendState = s.reevalSendState()
|
|
if !s.processSendLoss() || s.sendPktSeq.Seq%16 == 0 {
|
|
s.processSendExpire()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *udtSocketSend) reevalSendState() sendState {
|
|
if s.sndEvent != nil {
|
|
return sendStateSending
|
|
}
|
|
// Do we have too many unacknowledged packets for us to send any more?
|
|
if s.sendPktPend != nil {
|
|
congestWindow := uint(s.congestWindow.get())
|
|
cwnd := s.flowWindowSize
|
|
if cwnd > congestWindow {
|
|
cwnd = congestWindow
|
|
}
|
|
if uint(len(s.sendPktPend)) >= cwnd {
|
|
return sendStateWaiting
|
|
}
|
|
}
|
|
return sendStateIdle
|
|
}
|
|
|
|
// try to pack a new data packet and send it
|
|
func (s *udtSocketSend) processDataMsg(isFirst bool, inChan <-chan sendMessage) {
|
|
for s.msgPartialSend != nil {
|
|
partialSend := s.msgPartialSend
|
|
state := packet.MbOnly
|
|
if s.socket.isDatagram {
|
|
if isFirst {
|
|
state = packet.MbFirst
|
|
} else {
|
|
state = packet.MbMiddle
|
|
}
|
|
}
|
|
if isFirst || !s.socket.isDatagram {
|
|
s.msgSeq++
|
|
}
|
|
|
|
mtu := int(s.socket.maxPacketSize) - 16
|
|
msgLen := len(partialSend.content)
|
|
|
|
dp := &packet.DataPacket{
|
|
Seq: s.sendPktSeq,
|
|
}
|
|
|
|
if msgLen >= mtu {
|
|
// we are full -- send what we can and leave the rest
|
|
dp.Data = partialSend.content[0:mtu]
|
|
if msgLen == mtu {
|
|
s.msgPartialSend = nil
|
|
} else {
|
|
s.msgPartialSend = &sendMessage{content: partialSend.content[mtu:], tim: partialSend.tim, ttl: partialSend.ttl}
|
|
}
|
|
} else {
|
|
// we are not full -- send only if this is a datagram or there's nothing obvious left
|
|
if s.socket.isDatagram {
|
|
// datagram
|
|
if isFirst {
|
|
state = packet.MbOnly
|
|
} else {
|
|
state = packet.MbLast
|
|
}
|
|
} else {
|
|
// streaming socket
|
|
select {
|
|
case morePartialSend, ok := <-inChan:
|
|
if ok {
|
|
// we have more data, concat and try again
|
|
s.msgPartialSend = &sendMessage{
|
|
content: append(s.msgPartialSend.content, morePartialSend.content...),
|
|
tim: s.msgPartialSend.tim,
|
|
ttl: s.msgPartialSend.ttl,
|
|
}
|
|
continue
|
|
}
|
|
default:
|
|
// nothing immediately available, just send what we have
|
|
}
|
|
}
|
|
|
|
partialSend = s.msgPartialSend
|
|
dp.Data = partialSend.content
|
|
s.msgPartialSend = nil
|
|
}
|
|
|
|
s.sendPktSeq.Incr()
|
|
dp.SetMessageData(state, !s.socket.isDatagram, s.msgSeq)
|
|
s.sendDataPacket(sendPacketEntry{pkt: dp, tim: partialSend.tim, ttl: partialSend.ttl}, false)
|
|
}
|
|
}
|
|
|
|
// If the sender's loss list is not empty, retransmit the first packet in the list and remove it from the list.
|
|
func (s *udtSocketSend) processSendLoss() bool {
|
|
if s.sendLossList == nil || s.sendPktPend == nil {
|
|
return false
|
|
}
|
|
|
|
var dp *sendPacketEntry
|
|
for {
|
|
minLoss, minLossIdx := s.sendLossList.Min(s.recvAckSeq, s.sendPktSeq)
|
|
if minLossIdx < 0 {
|
|
// empty loss list? shouldn't really happen as we don't keep empty lists, but check for it anyhow
|
|
return false
|
|
}
|
|
|
|
heap.Remove(&s.sendLossList, minLossIdx)
|
|
if len(s.sendLossList) == 0 {
|
|
s.sendLossList = nil
|
|
}
|
|
|
|
dp, _ = s.sendPktPend.Find(minLoss)
|
|
if dp == nil {
|
|
// can't find record of this packet, not much we can do really
|
|
continue
|
|
}
|
|
|
|
if dp.ttl != 0 && time.Now().Add(dp.ttl).After(dp.tim) {
|
|
// this packet has expired, ignore
|
|
continue
|
|
}
|
|
|
|
break
|
|
}
|
|
|
|
s.sendDataPacket(*dp, true)
|
|
return true
|
|
}
|
|
|
|
// evaluate our pending packet list to see if we have any expired messages
|
|
func (s *udtSocketSend) processSendExpire() bool {
|
|
if s.sendPktPend == nil {
|
|
return false
|
|
}
|
|
|
|
pktPend := make([]sendPacketEntry, len(s.sendPktPend))
|
|
copy(pktPend, s.sendPktPend)
|
|
for _, p := range pktPend {
|
|
if p.ttl != 0 && time.Now().Add(p.ttl).After(p.tim) {
|
|
// this message has expired, drop it
|
|
_, _, msgNo := p.pkt.GetMessageData()
|
|
dropMsg := &packet.MsgDropReqPacket{
|
|
MsgID: msgNo,
|
|
FirstSeq: p.pkt.Seq,
|
|
LastSeq: p.pkt.Seq,
|
|
}
|
|
|
|
// find the other packets in this message
|
|
for _, op := range pktPend {
|
|
_, _, otherMsgNo := op.pkt.GetMessageData()
|
|
if otherMsgNo == msgNo {
|
|
if dropMsg.FirstSeq.BlindDiff(p.pkt.Seq) > 0 {
|
|
dropMsg.FirstSeq = p.pkt.Seq
|
|
}
|
|
if dropMsg.LastSeq.BlindDiff(p.pkt.Seq) < 0 {
|
|
dropMsg.LastSeq = p.pkt.Seq
|
|
}
|
|
}
|
|
if s.sendLossList != nil {
|
|
if _, slIdx := s.sendLossList.Find(p.pkt.Seq); slIdx >= 0 {
|
|
heap.Remove(&s.sendLossList, slIdx)
|
|
}
|
|
}
|
|
}
|
|
if s.sendLossList != nil && len(s.sendLossList) == 0 {
|
|
s.sendLossList = nil
|
|
}
|
|
|
|
s.sendPacket <- dropMsg
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// we have a packed packet and a green light to send, so lets send this and mark it
|
|
func (s *udtSocketSend) sendDataPacket(dp sendPacketEntry, isResend bool) {
|
|
if s.sendPktPend == nil {
|
|
s.sendPktPend = sendPacketHeap{dp}
|
|
heap.Init(&s.sendPktPend)
|
|
} else {
|
|
heap.Push(&s.sendPktPend, dp)
|
|
}
|
|
|
|
s.socket.cong.onDataPktSent(dp.pkt.Seq)
|
|
s.sendPacket <- dp.pkt
|
|
|
|
// have we exceeded our recipient's window size?
|
|
s.sendState = s.reevalSendState()
|
|
if s.sendState == sendStateWaiting {
|
|
return
|
|
}
|
|
|
|
if !isResend && dp.pkt.Seq.Seq%16 == 0 {
|
|
s.processSendExpire()
|
|
return
|
|
}
|
|
|
|
snd := s.sndPeriod.get()
|
|
if snd > 0 {
|
|
s.sndEvent = time.After(snd)
|
|
s.sendState = sendStateSending
|
|
}
|
|
}
|
|
|
|
// ingestLightAck is called to process a "light" ACK packet
|
|
func (s *udtSocketSend) ingestLightAck(p *packet.LightAckPacket, now time.Time) {
|
|
// Update the largest acknowledged sequence number.
|
|
|
|
pktSeqHi := p.PktSeqHi
|
|
diff := pktSeqHi.BlindDiff(s.recvAckSeq)
|
|
if diff > 0 {
|
|
s.flowWindowSize += uint(diff)
|
|
s.recvAckSeq = pktSeqHi
|
|
}
|
|
}
|
|
|
|
func (s *udtSocketSend) assertValidSentPktID(pktType string, pktSeq packet.PacketID) bool {
|
|
if s.sendPktSeq.BlindDiff(pktSeq) < 0 {
|
|
s.shutdownEvent <- shutdownMessage{sockState: sockStateCorrupted, permitLinger: false,
|
|
err: fmt.Errorf("FAULT: Received an %s for packet %d, but the largest packet we've sent has been %d", pktType, pktSeq.Seq, s.sendPktSeq.Seq)}
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// ingestAck is called to process an ACK packet
|
|
func (s *udtSocketSend) ingestAck(p *packet.AckPacket, now time.Time) {
|
|
// Update the largest acknowledged sequence number.
|
|
|
|
// Send back an ACK2 with the same ACK sequence number in this ACK.
|
|
if s.ack2SentEvent == nil && p.AckSeqNo == s.sentAck2 {
|
|
s.sentAck2 = p.AckSeqNo
|
|
s.sendPacket <- &packet.Ack2Packet{AckSeqNo: p.AckSeqNo}
|
|
s.ack2SentEvent = time.After(s.socket.Config.SynTime)
|
|
}
|
|
|
|
pktSeqHi := p.PktSeqHi
|
|
if !s.assertValidSentPktID("ACK", pktSeqHi) {
|
|
return
|
|
}
|
|
diff := pktSeqHi.BlindDiff(s.recvAckSeq)
|
|
if diff <= 0 {
|
|
return
|
|
}
|
|
|
|
oldAckSeq := s.recvAckSeq
|
|
s.flowWindowSize = uint(p.BuffAvail)
|
|
s.recvAckSeq = pktSeqHi
|
|
|
|
// Update RTT and RTTVar.
|
|
s.socket.applyRTT(uint(p.Rtt))
|
|
|
|
// Update flow window size.
|
|
if p.IncludeLink {
|
|
s.socket.applyReceiveRates(uint(p.PktRecvRate), uint(p.EstLinkCap))
|
|
}
|
|
|
|
s.socket.cong.onACK(pktSeqHi)
|
|
|
|
// Update packet arrival rate: A = (A * 7 + a) / 8, where a is the value carried in the ACK.
|
|
// Update estimated link capacity: B = (B * 7 + b) / 8, where b is the value carried in the ACK.
|
|
|
|
// Update sender's buffer (by releasing the buffer that has been acknowledged).
|
|
if s.sendPktPend != nil {
|
|
for {
|
|
minLoss, minLossIdx := s.sendPktPend.Min(oldAckSeq, s.sendPktSeq)
|
|
if pktSeqHi.BlindDiff(minLoss.Seq) >= 0 || minLossIdx < 0 {
|
|
break
|
|
}
|
|
heap.Remove(&s.sendPktPend, minLossIdx)
|
|
}
|
|
if len(s.sendPktPend) == 0 {
|
|
s.sendPktPend = nil
|
|
}
|
|
}
|
|
|
|
// Update sender's loss list (by removing all those that has been acknowledged).
|
|
if s.sendLossList != nil {
|
|
for {
|
|
minLoss, minLossIdx := s.sendLossList.Min(oldAckSeq, s.sendPktSeq)
|
|
if pktSeqHi.BlindDiff(minLoss) >= 0 || minLossIdx < 0 {
|
|
break
|
|
}
|
|
heap.Remove(&s.sendLossList, minLossIdx)
|
|
}
|
|
if len(s.sendLossList) == 0 {
|
|
s.sendLossList = nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// ingestNak is called to process an NAK packet
|
|
func (s *udtSocketSend) ingestNak(p *packet.NakPacket, now time.Time) {
|
|
newLossList := make([]packet.PacketID, 0)
|
|
clen := len(p.CmpLossInfo)
|
|
for idx := 0; idx < clen; idx++ {
|
|
thisEntry := p.CmpLossInfo[idx]
|
|
if thisEntry&0x80000000 != 0 {
|
|
thisPktID := packet.PacketID{Seq: thisEntry & 0x7FFFFFFF}
|
|
if idx+1 == clen {
|
|
s.shutdownEvent <- shutdownMessage{sockState: sockStateCorrupted, permitLinger: false,
|
|
err: fmt.Errorf("FAULT: While unpacking a NAK, the last entry (%x) was describing a start-of-range", thisEntry)}
|
|
return
|
|
}
|
|
if !s.assertValidSentPktID("NAK", thisPktID) {
|
|
return
|
|
}
|
|
lastEntry := p.CmpLossInfo[idx+1]
|
|
if lastEntry&0x80000000 != 0 {
|
|
s.shutdownEvent <- shutdownMessage{sockState: sockStateCorrupted, permitLinger: false,
|
|
err: fmt.Errorf("FAULT: While unpacking a NAK, a start-of-range (%x) was followed by another start-of-range (%x)", thisEntry, lastEntry)}
|
|
return
|
|
}
|
|
lastPktID := packet.PacketID{Seq: lastEntry}
|
|
if !s.assertValidSentPktID("NAK", lastPktID) {
|
|
return
|
|
}
|
|
idx++
|
|
for span := thisPktID; span != lastPktID; span.Incr() {
|
|
newLossList = append(newLossList, span)
|
|
}
|
|
} else {
|
|
thisPktID := packet.PacketID{Seq: thisEntry}
|
|
if !s.assertValidSentPktID("NAK", thisPktID) {
|
|
return
|
|
}
|
|
newLossList = append(newLossList, thisPktID)
|
|
}
|
|
}
|
|
|
|
s.socket.cong.onNAK(newLossList)
|
|
|
|
if s.sendLossList == nil {
|
|
s.sendLossList = newLossList
|
|
heap.Init(&s.sendLossList)
|
|
} else {
|
|
llen := len(newLossList)
|
|
for idx := 0; idx < llen; idx++ {
|
|
heap.Push(&s.sendLossList, newLossList[idx])
|
|
}
|
|
}
|
|
|
|
s.sendState = sendStateProcessDrop // immediately restart transmission
|
|
}
|
|
|
|
// ingestCongestion is called to process a (retired?) Congestion packet
|
|
func (s *udtSocketSend) ingestCongestion(p *packet.CongestionPacket, now time.Time) {
|
|
// One way packet delay is increasing, so decrease the sending rate
|
|
// this is very rough (not atomic, doesn't inform congestion) but this is a deprecated message in any case
|
|
s.sndPeriod.set(s.sndPeriod.get() * 1125 / 1000)
|
|
//m_iLastDecSeq = s.sendPktSeq
|
|
}
|
|
|
|
func (s *udtSocketSend) resetEXP(now time.Time) {
|
|
s.lastRecvTime = now
|
|
|
|
var nextExpDurn time.Duration
|
|
rtoPeriod := s.rtoPeriod.get()
|
|
if rtoPeriod > 0 {
|
|
nextExpDurn = rtoPeriod
|
|
} else {
|
|
rtt, rttVar := s.socket.getRTT()
|
|
nextExpDurn = (time.Duration(s.expCount*(rtt+4*rttVar))*time.Microsecond + s.socket.Config.SynTime)
|
|
minExpTime := time.Duration(s.expCount) * minEXPinterval
|
|
if nextExpDurn < minExpTime {
|
|
nextExpDurn = minExpTime
|
|
}
|
|
}
|
|
s.expTimerEvent = time.After(nextExpDurn)
|
|
}
|
|
|
|
// we've just had the EXP timer expire, see what we can do to recover this
|
|
func (s *udtSocketSend) expEvent(currTime time.Time) {
|
|
|
|
// Haven't receive any information from the peer, is it dead?!
|
|
// timeout: at least 16 expirations and must be greater than 10 seconds
|
|
if (s.expCount > 16) && (currTime.Sub(s.lastRecvTime) > 5*time.Second) {
|
|
// Connection is broken.
|
|
s.shutdownEvent <- shutdownMessage{sockState: sockStateTimeout, permitLinger: true}
|
|
return
|
|
}
|
|
|
|
// sender: Insert all the packets sent after last received acknowledgement into the sender loss list.
|
|
// recver: Send out a keep-alive packet
|
|
if s.sendPktPend != nil {
|
|
if s.sendPktPend != nil && s.sendLossList == nil {
|
|
// resend all unacknowledged packets on timeout, but only if there is no packet in the loss list
|
|
newLossList := make([]packet.PacketID, 0)
|
|
for span := s.recvAckSeq.Add(1); span != s.sendPktSeq.Add(1); span.Incr() {
|
|
newLossList = append(newLossList, span)
|
|
}
|
|
s.sendLossList = newLossList
|
|
heap.Init(&s.sendLossList)
|
|
}
|
|
s.socket.cong.onTimeout()
|
|
s.sendState = sendStateProcessDrop // immediately restart transmission
|
|
} else {
|
|
s.sendPacket <- &packet.KeepAlivePacket{}
|
|
}
|
|
|
|
s.expCount++
|
|
// Reset last response time since we just sent a heart-beat.
|
|
s.resetEXP(currTime)
|
|
}
|