admin.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. package oscar
  2. import (
  3. "bytes"
  4. "context"
  5. "errors"
  6. "fmt"
  7. "io"
  8. "log/slog"
  9. "net"
  10. "sync"
  11. "github.com/mk6i/retro-aim-server/config"
  12. "github.com/mk6i/retro-aim-server/server/oscar/middleware"
  13. "github.com/mk6i/retro-aim-server/state"
  14. "github.com/mk6i/retro-aim-server/wire"
  15. )
  16. // AdminServer provides client connection lifecycle management for the BOS
  17. // service.
  18. type AdminServer struct {
  19. AuthService
  20. Handler
  21. ListenAddr string
  22. Logger *slog.Logger
  23. OnlineNotifier
  24. config.Config
  25. }
  26. // Start starts a TCP server and listens for connections. The initial
  27. // authentication handshake sequences are handled by this method. The remaining
  28. // requests are relayed to BOSRouter.
  29. func (rt AdminServer) Start(ctx context.Context) error {
  30. listener, err := net.Listen("tcp", rt.ListenAddr)
  31. if err != nil {
  32. return fmt.Errorf("unable to start admin server: %w", err)
  33. }
  34. go func() {
  35. <-ctx.Done()
  36. listener.Close()
  37. }()
  38. rt.Logger.Info("starting server", "listen_host", rt.ListenAddr, "oscar_host", rt.Config.OSCARHost)
  39. wg := sync.WaitGroup{}
  40. for {
  41. conn, err := listener.Accept()
  42. if err != nil {
  43. if errors.Is(err, net.ErrClosed) {
  44. break
  45. }
  46. rt.Logger.Error("accept failed", "err", err.Error())
  47. continue
  48. }
  49. wg.Add(1)
  50. go func() {
  51. defer wg.Done()
  52. connCtx := context.WithValue(ctx, "ip", conn.RemoteAddr().String())
  53. rt.Logger.DebugContext(connCtx, "accepted connection")
  54. if err := rt.handleNewConnection(connCtx, conn); err != nil {
  55. rt.Logger.Info("user session failed", "err", err.Error())
  56. }
  57. }()
  58. }
  59. if !waitForShutdown(&wg) {
  60. rt.Logger.Error("shutdown complete, but connections didn't close cleanly")
  61. } else {
  62. rt.Logger.Info("shutdown complete")
  63. }
  64. return nil
  65. }
  66. func (rt AdminServer) handleNewConnection(ctx context.Context, rwc io.ReadWriteCloser) error {
  67. flapc := wire.NewFlapClient(100, rwc, rwc)
  68. if err := flapc.SendSignonFrame(nil); err != nil {
  69. return err
  70. }
  71. flap, err := flapc.ReceiveSignonFrame()
  72. if err != nil {
  73. return err
  74. }
  75. authCookie, ok := flap.Bytes(wire.OServiceTLVTagsLoginCookie)
  76. if !ok {
  77. return errors.New("unable to get session id from payload")
  78. }
  79. sess, err := rt.RetrieveBOSSession(authCookie)
  80. if err != nil {
  81. return err
  82. }
  83. if sess == nil {
  84. return errors.New("session not found")
  85. }
  86. defer func() {
  87. rwc.Close()
  88. }()
  89. ctx = context.WithValue(ctx, "screenName", sess.IdentScreenName())
  90. msg := rt.OnlineNotifier.HostOnline()
  91. if err := flapc.SendSNAC(msg.Frame, msg.Body); err != nil {
  92. return err
  93. }
  94. return dispatchIncomingMessagesSimple(ctx, sess, flapc, rwc, rt.Logger, rt.Handler)
  95. }
  96. func dispatchIncomingMessagesSimple(ctx context.Context, sess *state.Session, flapc *wire.FlapClient, r io.Reader, logger *slog.Logger, router Handler) error {
  97. defer func() {
  98. logger.InfoContext(ctx, "user disconnected")
  99. }()
  100. // buffered so that the go routine has room to exit
  101. msgCh := make(chan wire.FLAPFrame, 1)
  102. errCh := make(chan error, 1)
  103. // consume flap frames
  104. go func() {
  105. defer close(msgCh)
  106. defer close(errCh)
  107. for {
  108. frame := wire.FLAPFrame{}
  109. if err := wire.UnmarshalBE(&frame, r); err != nil {
  110. errCh <- err
  111. return
  112. }
  113. msgCh <- frame
  114. }
  115. }()
  116. for {
  117. select {
  118. case flap, ok := <-msgCh:
  119. if !ok {
  120. return nil
  121. }
  122. switch flap.FrameType {
  123. case wire.FLAPFrameData:
  124. flapBuf := bytes.NewBuffer(flap.Payload)
  125. inFrame := wire.SNACFrame{}
  126. if err := wire.UnmarshalBE(&inFrame, flapBuf); err != nil {
  127. return err
  128. }
  129. // route a client request to the appropriate service handler. the
  130. // handler may write a response to the client connection.
  131. if err := router.Handle(ctx, sess, inFrame, flapBuf, flapc); err != nil {
  132. middleware.LogRequestError(ctx, logger, inFrame, err)
  133. if errors.Is(err, ErrRouteNotFound) {
  134. if err1 := sendInvalidSNACErr(inFrame, flapc); err1 != nil {
  135. return errors.Join(err1, err)
  136. }
  137. break
  138. }
  139. return err
  140. }
  141. case wire.FLAPFrameSignon:
  142. return fmt.Errorf("shouldn't get FLAPFrameSignon. flap: %v", flap)
  143. case wire.FLAPFrameError:
  144. return fmt.Errorf("got FLAPFrameError. flap: %v", flap)
  145. case wire.FLAPFrameSignoff:
  146. logger.InfoContext(ctx, "got FLAPFrameSignoff", "flap", flap)
  147. return nil
  148. case wire.FLAPFrameKeepAlive:
  149. logger.DebugContext(ctx, "keepalive heartbeat")
  150. default:
  151. return fmt.Errorf("got unknown FLAP frame type. flap: %v", flap)
  152. }
  153. case <-ctx.Done():
  154. // application is shutting down
  155. if err := flapc.Disconnect(); err != nil {
  156. return fmt.Errorf("unable to gracefully disconnect user. %w", err)
  157. }
  158. return nil
  159. case err := <-errCh:
  160. if !errors.Is(io.EOF, err) {
  161. logger.ErrorContext(ctx, "client disconnected with error", "err", err)
  162. }
  163. return nil
  164. }
  165. }
  166. }