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 }