github.com/geph-official/geph2@v0.22.6-0.20210211030601-f527cb59b0df/libs/niaucchi4/e2e_session.go (about)

     1  package niaucchi4
     2  
     3  import (
     4  	"encoding/binary"
     5  	"errors"
     6  	"log"
     7  	"math"
     8  	"net"
     9  	"strings"
    10  	"sync"
    11  	"time"
    12  
    13  	lru "github.com/hashicorp/golang-lru"
    14  	"github.com/minio/highwayhash"
    15  	"golang.org/x/time/rate"
    16  )
    17  
    18  type rtTracker struct {
    19  	tab   map[uint64]int
    20  	queue []uint64
    21  }
    22  
    23  func newRtTracker() *rtTracker {
    24  	return &rtTracker{
    25  		tab: make(map[uint64]int),
    26  	}
    27  }
    28  
    29  func (rtt *rtTracker) add(k uint64, v int) {
    30  	rtt.tab[k] = v
    31  	rtt.queue = append(rtt.queue, k)
    32  	if len(rtt.queue) > 1000 {
    33  		oldest := rtt.queue[0]
    34  		rtt.queue = rtt.queue[1:]
    35  		delete(rtt.tab, oldest)
    36  	}
    37  }
    38  
    39  func (rtt *rtTracker) get(k uint64) int {
    40  	z, ok := rtt.tab[k]
    41  	if !ok {
    42  		return -1
    43  	}
    44  	return z
    45  }
    46  
    47  type e2eSession struct {
    48  	remote        []net.Addr
    49  	info          []*e2eLinkInfo
    50  	sessid        SessionAddr
    51  	rdqueue       [][]byte
    52  	dupRateLimit  *rate.Limiter
    53  	infoRateLimit *rate.Limiter
    54  	lastSend      time.Time
    55  	lastRemid     int
    56  	recvDedup     *lru.Cache
    57  	sendDedup     *rtTracker
    58  	sendCallback  func(e2ePacket, net.Addr)
    59  
    60  	lock sync.Mutex
    61  }
    62  
    63  func newSession(sessid [16]byte, sendCallback func(e2ePacket, net.Addr)) *e2eSession {
    64  	cache, _ := lru.New(128)
    65  	return &e2eSession{
    66  		dupRateLimit:  rate.NewLimiter(10, 100),
    67  		infoRateLimit: rate.NewLimiter(30, 100),
    68  		recvDedup:     cache,
    69  		sendDedup:     newRtTracker(),
    70  		sessid:        sessid,
    71  		sendCallback:  sendCallback,
    72  	}
    73  }
    74  
    75  type e2eLinkInfo struct {
    76  	sendsn  uint64
    77  	acksn   uint64
    78  	recvsn  uint64
    79  	recvcnt uint64
    80  
    81  	recvsnRecent  uint32
    82  	recvcntRecent uint32
    83  
    84  	recvWindow replayWindow
    85  
    86  	txCount    uint64
    87  	rtxCount   uint64
    88  	remoteLoss float64
    89  	checkTime  time.Time
    90  
    91  	lastSendTime  time.Time
    92  	lastProbeTime time.Time
    93  	lastProbeSn   uint64
    94  	lastPing      int64
    95  	lastPingTime  time.Time
    96  
    97  	lastRecvTime time.Time
    98  }
    99  
   100  func (el *e2eLinkInfo) getScore() float64 {
   101  	// TODO send loss is what we actually need!
   102  	// recvLoss := math.Max(0, 1.0-float64(el.recvcnt)/(1+float64(el.recvsn)))
   103  	// return math.Max(float64(el.lastPing), float64(time.Since(el.lastSendTime).Milliseconds())) + recvLoss*100
   104  	now := time.Now()
   105  	if now.Sub(el.checkTime).Seconds() > 5 {
   106  		// ensure accurate measurement
   107  		if el.txCount > 1000 {
   108  			el.rtxCount /= 2
   109  			el.txCount /= 2
   110  			el.checkTime = now
   111  		}
   112  	}
   113  	pseudoPing := float64(el.lastPing) + math.Max(0, time.Since(el.lastRecvTime).Seconds()*1000-3000)
   114  	loss := float64(el.rtxCount) / (float64(el.txCount) + 1)
   115  	if el.remoteLoss >= 0 {
   116  		loss = el.remoteLoss
   117  	}
   118  	if loss > 1 {
   119  		loss = 1
   120  	}
   121  	return pseudoPing * (1 / (1.01 - math.Min(1, loss))) // intuition: expected retransmissions needed
   122  	// return el.longLoss
   123  }
   124  
   125  type e2ePacket struct {
   126  	Session SessionAddr
   127  	Sn      uint64
   128  	Ack     uint64
   129  	Body    []byte
   130  	Padding []byte
   131  }
   132  
   133  // LinkInfo describes info for a link.
   134  type LinkInfo struct {
   135  	RemoteIP      string
   136  	RecvCnt       int
   137  	Ping          int
   138  	LossPct       float64
   139  	RecentLossPct float64
   140  	Score         float64
   141  }
   142  
   143  // DebugInfo dumps out info about all the links.
   144  func (es *e2eSession) DebugInfo() (lii []LinkInfo) {
   145  	es.lock.Lock()
   146  	defer es.lock.Unlock()
   147  	for i, nfo := range es.info {
   148  		lii = append(lii, LinkInfo{
   149  			RemoteIP:      strings.Split(es.remote[i].String(), ":")[0],
   150  			RecvCnt:       int(nfo.recvcnt),
   151  			Ping:          int(nfo.lastPing),
   152  			LossPct:       math.Max(0, 1.0-float64(nfo.recvcnt)/(1+float64(nfo.recvsn))),
   153  			RecentLossPct: math.Max(0, 1.0-float64(nfo.recvcntRecent)/(1+float64(nfo.recvsnRecent))),
   154  			Score:         nfo.getScore(),
   155  		})
   156  	}
   157  	return
   158  }
   159  
   160  func (es *e2eSession) AddPath(host net.Addr) {
   161  	es.lock.Lock()
   162  	defer es.lock.Unlock()
   163  	for _, h := range es.remote {
   164  		if h.String() == host.String() {
   165  			return
   166  		}
   167  	}
   168  	if doLogging {
   169  		log.Printf("N4: [%p] adding new path %v", es, host)
   170  	}
   171  	es.remote = append(es.remote, host)
   172  	es.info = append(es.info, &e2eLinkInfo{lastPing: 10000000, lastRecvTime: time.Now(), remoteLoss: -1})
   173  }
   174  
   175  func (es *e2eSession) processStats(pkt e2ePacket, remid int) {
   176  	recvd := binary.LittleEndian.Uint32(pkt.Body[:4])
   177  	total := binary.LittleEndian.Uint32(pkt.Body[4:])
   178  	loss := 1 - float64(recvd)/(float64(total)+1)
   179  	rid := es.info[remid]
   180  	if rid.remoteLoss < 0 || (loss < rid.remoteLoss && total > 50) {
   181  		rid.remoteLoss = loss
   182  	} else {
   183  		rid.remoteLoss = 0.9*rid.remoteLoss + 0.1*loss
   184  	}
   185  }
   186  
   187  // Input processes a packet through the e2e session state.
   188  func (es *e2eSession) Input(pkt e2ePacket, source net.Addr) {
   189  	es.lock.Lock()
   190  	defer es.lock.Unlock()
   191  	sendInfo := func(remid int) {
   192  		now := time.Now()
   193  		nfo := es.info[remid]
   194  		if nfo.recvsnRecent > 1000 && now.Sub(nfo.checkTime).Seconds() > 5 {
   195  			nfo.recvsnRecent /= 2
   196  			nfo.recvcntRecent /= 2
   197  			nfo.checkTime = now
   198  		}
   199  		buf := make([]byte, 8)
   200  		binary.LittleEndian.PutUint32(buf[4:], nfo.recvsnRecent)
   201  		binary.LittleEndian.PutUint32(buf[:4], nfo.recvcntRecent)
   202  		es.rawSend(false, remid, buf)
   203  	}
   204  	if pkt.Session != es.sessid {
   205  		log.Println("pkt.Session =", pkt.Session, "; es.sessid =", es.sessid)
   206  		panic("wrong sessid passed to Input")
   207  	}
   208  	// first find the remote
   209  	remid := -1
   210  	for i, v := range es.remote {
   211  		if v.String() == source.String() {
   212  			remid = i
   213  			break
   214  		}
   215  	}
   216  	if remid < 0 {
   217  		if doLogging {
   218  			log.Println("N4: e2eSession.Input() failed to find remid")
   219  		}
   220  		return
   221  	}
   222  	// parse the stuff
   223  	if !es.info[remid].recvWindow.check(pkt.Sn) {
   224  		if doLogging {
   225  			log.Println("N4: discarding", pkt.Sn, "<", es.info[remid].recvsn)
   226  		}
   227  		return
   228  	}
   229  	if es.info[remid].recvsn < pkt.Sn {
   230  		es.info[remid].recvsnRecent += uint32(pkt.Sn - es.info[remid].recvsn)
   231  		es.info[remid].recvsn = pkt.Sn
   232  		es.info[remid].acksn = pkt.Ack
   233  	}
   234  
   235  	es.info[remid].recvcnt++
   236  	es.info[remid].recvcntRecent++
   237  	if len(pkt.Body) > 8 {
   238  		bodyHash := highwayhash.Sum128(pkt.Body, make([]byte, 32))
   239  		if es.recvDedup.Contains(bodyHash) {
   240  		} else {
   241  			es.recvDedup.Add(bodyHash, true)
   242  			es.rdqueue = append(es.rdqueue, pkt.Body)
   243  		}
   244  	}
   245  	if len(pkt.Body) == 8 {
   246  		es.processStats(pkt, remid)
   247  	}
   248  	nfo := es.info[remid]
   249  	now := time.Now()
   250  	if nfo.acksn > nfo.lastProbeSn && nfo.acksn > 10 {
   251  		pingSample := now.Sub(nfo.lastProbeTime).Milliseconds()
   252  		if pingSample < nfo.lastPing || now.Sub(nfo.lastPingTime).Seconds() > 60 {
   253  			nfo.lastPing = pingSample
   254  			nfo.lastPingTime = now
   255  		}
   256  		nfo.lastProbeSn = nfo.sendsn
   257  		nfo.lastProbeTime = now
   258  	}
   259  	nfo.lastRecvTime = now
   260  
   261  	if es.infoRateLimit.Allow() {
   262  		sendInfo(remid)
   263  	}
   264  }
   265  
   266  func (es *e2eSession) rawSend(rtd bool, remid int, payload []byte) {
   267  	now := time.Now()
   268  	if len(payload) > 1000 {
   269  		bodyHash := highwayhash.Sum64(payload[256:], make([]byte, 32))
   270  		val := es.sendDedup.get(bodyHash)
   271  		if val >= 0 {
   272  			//devalFactor := math.Pow(0.9, now.Sub(es.info[val].lastSendTime).Seconds())
   273  			es.info[val].rtxCount++
   274  			es.info[val].lastSendTime = now
   275  		} else {
   276  			//devalFactor := math.Pow(0.9, now.Sub(es.info[remid].lastSendTime).Seconds())
   277  			es.sendDedup.add(bodyHash, remid)
   278  			es.info[remid].txCount++
   279  			es.info[remid].lastSendTime = now
   280  		}
   281  	}
   282  	// create pkt
   283  	toSend := e2ePacket{
   284  		Session: es.sessid,
   285  		Sn:      es.info[remid].sendsn,
   286  		Ack:     es.info[remid].recvsn + 1,
   287  		Body:    payload,
   288  	}
   289  	es.info[remid].sendsn++
   290  	dest := es.remote[remid]
   291  	es.sendCallback(toSend, dest)
   292  }
   293  
   294  // Send sends a packet. It returns instructions to where the packet should be sent etc
   295  func (es *e2eSession) Send(payload []byte) (err error) {
   296  	es.lock.Lock()
   297  	defer es.lock.Unlock()
   298  	now := time.Now()
   299  	// find the right destination
   300  	if es.dupRateLimit.AllowN(time.Now(), len(es.remote)) {
   301  		//log.Println("sending small payload", len(payload), "to all paths")
   302  		for remid := range es.remote {
   303  			es.rawSend(false, remid, payload)
   304  		}
   305  	}
   306  	remid := -1
   307  	if time.Since(es.lastSend).Seconds() > 0.1 {
   308  		lowPoint := -1.0
   309  		for i, li := range es.info {
   310  			if score := li.getScore(); score < lowPoint || lowPoint < 0 {
   311  				lowPoint = score
   312  				remid = i
   313  			}
   314  		}
   315  		if doLogging {
   316  			log.Println("N4: selected", es.remote[remid], "with score", es.info[remid].getScore())
   317  			go func() {
   318  				for remid, v := range es.DebugInfo() {
   319  					log.Printf("%v %v %v/%v", v.RemoteIP, v.Ping,
   320  						es.info[remid].rtxCount, es.info[remid].txCount)
   321  				}
   322  			}()
   323  		}
   324  		if remid == -1 {
   325  			err = errors.New("cannot find any path")
   326  			return
   327  		}
   328  		es.lastSend = now
   329  	} else {
   330  		remid = es.lastRemid
   331  	}
   332  	es.lastRemid = remid
   333  	es.rawSend(true, remid, payload)
   334  	return
   335  }
   336  
   337  // FlushReadQueue flushes the entire read queue.
   338  func (es *e2eSession) FlushReadQueue(onPacket func([]byte)) {
   339  	es.lock.Lock()
   340  	defer es.lock.Unlock()
   341  	for _, b := range es.rdqueue {
   342  		onPacket(b)
   343  	}
   344  	es.rdqueue = es.rdqueue[:0]
   345  }