353 lines
6.6 KiB
Go
353 lines
6.6 KiB
Go
package raknet
|
|
|
|
import (
|
|
"net"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type Handler interface {
|
|
OnConnect(s *Session)
|
|
OnPacket(s *Session, payload []byte)
|
|
OnDisconnect(s *Session)
|
|
}
|
|
|
|
type Server struct {
|
|
mu sync.RWMutex
|
|
conn *net.UDPConn
|
|
sessions map[string]*Session
|
|
guid int64
|
|
motdFunc func() string
|
|
handler Handler
|
|
running bool
|
|
}
|
|
|
|
func NewServer(guid int64, motdFunc func() string, handler Handler) *Server {
|
|
return &Server{
|
|
guid: guid,
|
|
motdFunc: motdFunc,
|
|
handler: handler,
|
|
sessions: make(map[string]*Session),
|
|
}
|
|
}
|
|
|
|
func (s *Server) Start(ip string, port int) error {
|
|
addr := &net.UDPAddr{IP: net.ParseIP(ip), Port: port}
|
|
conn, err := net.ListenUDP("udp4", addr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.conn = conn
|
|
s.running = true
|
|
go s.readLoop()
|
|
go s.tickLoop()
|
|
return nil
|
|
}
|
|
|
|
func (s *Server) Shutdown() {
|
|
s.running = false
|
|
if s.conn != nil {
|
|
s.conn.Close()
|
|
}
|
|
}
|
|
|
|
func (s *Server) readLoop() {
|
|
buf := make([]byte, 2048)
|
|
for s.running {
|
|
n, addr, err := s.conn.ReadFromUDP(buf)
|
|
if err != nil {
|
|
if !s.running {
|
|
return
|
|
}
|
|
continue
|
|
}
|
|
data := make([]byte, n)
|
|
copy(data, buf[:n])
|
|
go func(a *net.UDPAddr, d []byte) {
|
|
defer func() { recover() }()
|
|
s.handlePacket(a, d)
|
|
}(addr, data)
|
|
}
|
|
}
|
|
|
|
func (s *Server) tickLoop() {
|
|
ticker := time.NewTicker(50 * time.Millisecond)
|
|
defer ticker.Stop()
|
|
for s.running {
|
|
<-ticker.C
|
|
s.tick()
|
|
}
|
|
}
|
|
|
|
func (s *Server) tick() {
|
|
s.mu.RLock()
|
|
sessions := make([]*Session, 0, len(s.sessions))
|
|
for _, sess := range s.sessions {
|
|
sessions = append(sessions, sess)
|
|
}
|
|
s.mu.RUnlock()
|
|
|
|
for _, sess := range sessions {
|
|
sess.FlushACKs()
|
|
if sess.TimedOut() {
|
|
s.removeSession(sess)
|
|
if s.handler != nil {
|
|
s.handler.OnDisconnect(sess)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Server) handlePacket(addr *net.UDPAddr, data []byte) {
|
|
if len(data) == 0 {
|
|
return
|
|
}
|
|
id := data[0]
|
|
|
|
switch {
|
|
case id == 0x01 || id == 0x02:
|
|
ts, _, ok := ParseUnconnectedPing(data)
|
|
if !ok {
|
|
return
|
|
}
|
|
pong := BuildUnconnectedPong(ts, s.guid, s.motdFunc())
|
|
s.conn.WriteToUDP(pong, addr)
|
|
|
|
case id == 0x05:
|
|
proto, mtu, ok := ParseOCR1(data)
|
|
if !ok || proto != 8 {
|
|
return
|
|
}
|
|
if mtu > MaxMTU {
|
|
mtu = MaxMTU
|
|
}
|
|
s.conn.WriteToUDP(BuildOpenConnectionReply1(s.guid, mtu), addr)
|
|
|
|
case id == 0x07:
|
|
clientGUID, _, mtu, ok := ParseOCR2(data)
|
|
if !ok {
|
|
return
|
|
}
|
|
if mtu > MaxMTU {
|
|
mtu = MaxMTU
|
|
}
|
|
key := addr.String()
|
|
s.mu.Lock()
|
|
if _, exists := s.sessions[key]; exists {
|
|
s.mu.Unlock()
|
|
return
|
|
}
|
|
sess := NewSession(s.conn, addr, clientGUID, mtu)
|
|
s.sessions[key] = sess
|
|
s.mu.Unlock()
|
|
s.conn.WriteToUDP(BuildOpenConnectionReply2(s.guid, *addr, mtu), addr)
|
|
|
|
case id >= 0x80 && id <= 0x8f:
|
|
sess := s.getSession(addr)
|
|
if sess == nil || sess.IsDisconnected() {
|
|
return
|
|
}
|
|
s.handleDatagram(sess, data)
|
|
|
|
case id == 0xc0:
|
|
sess := s.getSession(addr)
|
|
if sess != nil {
|
|
sess.HandleACK(data)
|
|
}
|
|
|
|
case id == 0xa0:
|
|
sess := s.getSession(addr)
|
|
if sess != nil {
|
|
sess.HandleNACK(data)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Server) handleDatagram(sess *Session, data []byte) {
|
|
dg, err := DecodeDatagram(data)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
sess.mu.Lock()
|
|
sess.lastRecv = time.Now()
|
|
|
|
if sess.recvSeqs[dg.SeqNum] {
|
|
sess.mu.Unlock()
|
|
return
|
|
}
|
|
sess.recvSeqs[dg.SeqNum] = true
|
|
sess.pendingACK = append(sess.pendingACK, dg.SeqNum)
|
|
|
|
if dg.SeqNum > sess.recvSeq+1 {
|
|
for n := sess.recvSeq + 1; n < dg.SeqNum; n++ {
|
|
if !sess.recvSeqs[n] {
|
|
sess.pendingNACK = append(sess.pendingNACK, n)
|
|
}
|
|
}
|
|
}
|
|
if dg.SeqNum >= sess.recvSeq {
|
|
sess.recvSeq = dg.SeqNum + 1
|
|
}
|
|
sess.mu.Unlock()
|
|
|
|
for _, f := range dg.Frames {
|
|
s.handleFrame(sess, f)
|
|
}
|
|
}
|
|
|
|
func (s *Server) handleFrame(sess *Session, f *Frame) {
|
|
if f.Split {
|
|
sess.mu.Lock()
|
|
buf, ok := sess.splitMap[f.SplitID]
|
|
if !ok {
|
|
buf = &splitBuf{count: f.SplitCount, parts: make(map[uint32][]byte)}
|
|
sess.splitMap[f.SplitID] = buf
|
|
}
|
|
buf.parts[f.SplitIndex] = f.Payload
|
|
assembled := false
|
|
var full []byte
|
|
if uint32(len(buf.parts)) >= buf.count {
|
|
for i := uint32(0); i < buf.count; i++ {
|
|
full = append(full, buf.parts[i]...)
|
|
}
|
|
delete(sess.splitMap, f.SplitID)
|
|
assembled = true
|
|
}
|
|
sess.mu.Unlock()
|
|
if assembled {
|
|
combined := &Frame{
|
|
Reliability: f.Reliability,
|
|
MessageIndex: f.MessageIndex,
|
|
OrderIndex: f.OrderIndex,
|
|
OrderChannel: f.OrderChannel,
|
|
Payload: full,
|
|
}
|
|
s.handleFrame(sess, combined)
|
|
}
|
|
return
|
|
}
|
|
|
|
if f.Reliability.IsOrdered() {
|
|
ch := int(f.OrderChannel)
|
|
sess.mu.Lock()
|
|
expected := sess.orderNext[ch]
|
|
if f.OrderIndex == expected {
|
|
sess.orderNext[ch]++
|
|
payload := f.Payload
|
|
var extras [][]byte
|
|
for {
|
|
next := sess.orderQueues[ch][sess.orderNext[ch]]
|
|
if next == nil {
|
|
break
|
|
}
|
|
delete(sess.orderQueues[ch], sess.orderNext[ch])
|
|
sess.orderNext[ch]++
|
|
extras = append(extras, next)
|
|
}
|
|
sess.mu.Unlock()
|
|
s.dispatch(sess, payload)
|
|
for _, e := range extras {
|
|
s.dispatch(sess, e)
|
|
}
|
|
} else if f.OrderIndex > expected {
|
|
sess.orderQueues[ch][f.OrderIndex] = f.Payload
|
|
sess.mu.Unlock()
|
|
} else {
|
|
sess.mu.Unlock()
|
|
}
|
|
return
|
|
}
|
|
|
|
s.dispatch(sess, f.Payload)
|
|
}
|
|
|
|
func (s *Server) dispatch(sess *Session, payload []byte) {
|
|
if len(payload) == 0 {
|
|
return
|
|
}
|
|
id := payload[0]
|
|
|
|
if id == 0x09 {
|
|
_, timestamp, ok := ParseConnectionRequest(payload)
|
|
if !ok {
|
|
return
|
|
}
|
|
accepted := BuildConnectionRequestAccepted(*sess.Addr, timestamp, sess.Timestamp())
|
|
sess.Send(accepted, ReliableOrdered, 0)
|
|
return
|
|
}
|
|
|
|
if id == 0x13 {
|
|
sess.SetConnected()
|
|
if s.handler != nil {
|
|
s.handler.OnConnect(sess)
|
|
}
|
|
return
|
|
}
|
|
|
|
if id == 0x15 {
|
|
s.removeSession(sess)
|
|
if s.handler != nil {
|
|
s.handler.OnDisconnect(sess)
|
|
}
|
|
return
|
|
}
|
|
|
|
if id == 0x00 {
|
|
t, ok := ParseConnectedPing(payload)
|
|
if ok {
|
|
pong := BuildConnectedPong(t, sess.Timestamp())
|
|
sess.Send(pong, Unreliable, 0)
|
|
}
|
|
return
|
|
}
|
|
|
|
if !sess.IsConnected() {
|
|
return
|
|
}
|
|
|
|
if s.handler != nil {
|
|
s.handler.OnPacket(sess, payload)
|
|
}
|
|
}
|
|
|
|
func (s *Server) getSession(addr *net.UDPAddr) *Session {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
return s.sessions[addr.String()]
|
|
}
|
|
|
|
func (s *Server) removeSession(sess *Session) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
delete(s.sessions, sess.Addr.String())
|
|
}
|
|
|
|
func (s *Server) GetSessionByGUID(guid int64) *Session {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
for _, sess := range s.sessions {
|
|
if sess.GUID == guid {
|
|
return sess
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Server) Count() int {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
return len(s.sessions)
|
|
}
|
|
|
|
func (s *Server) Sessions() []*Session {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
list := make([]*Session, 0, len(s.sessions))
|
|
for _, sess := range s.sessions {
|
|
list = append(list, sess)
|
|
}
|
|
return list
|
|
}
|