package session import ( "bufio" "encoding/json" "errors" "fmt" "io" "net" "net/http" "runtime" "sort" "sync" "sync/atomic" "time" C "github.com/xjasonlyu/tun2socks/constant" "github.com/gobuffalo/packr/v2" ) const maxClosedSessions = 100 type Server struct { sync.Mutex *http.Server ServeAddr string trafficUp int64 trafficDown int64 activeSessionMap sync.Map closedSessionList []Session } func New(addr string) *Server { return &Server{ ServeAddr: addr, } } func (s *Server) getSessions() (activeSessions, closedSessions []Session) { // Slice of active sessions s.activeSessionMap.Range(func(key, value interface{}) bool { session := value.(*Session) activeSessions = append(activeSessions, *session) return true }) // Slice of closed sessions s.Lock() defer s.Unlock() closedSessions = append([]Session(nil), s.closedSessionList...) return } func (s *Server) serveJSON(w http.ResponseWriter, _ *http.Request) { activeSessions, closedSessions := s.getSessions() // calculate traffic trafficUp := atomic.LoadInt64(&s.trafficUp) trafficDown := atomic.LoadInt64(&s.trafficDown) for _, session := range activeSessions { trafficUp += session.UploadBytes trafficDown += session.DownloadBytes } status := &Status{ platform(), C.Version, cpu(), mem(), uptime(), trafficUp + trafficDown, trafficUp, trafficDown, runtime.NumGoroutine(), activeSessions, closedSessions, } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(status) } func (s *Server) serveHTML(resp http.ResponseWriter, _ *http.Request) { activeSessions, closedSessions := s.getSessions() tablePrint := func(w io.Writer, sessions []Session) { // Sort by session start time. sort.Slice(sessions, func(i, j int) bool { return sessions[i].SessionStart.After(sessions[j].SessionStart) }) _, _ = fmt.Fprintf(w, "
\n") _, _ = fmt.Fprintf(w, "\n") for _, session := range sessions { _, _ = fmt.Fprintf(w, "\n", session.Process, session.Network, date(session.SessionStart), duration(session.SessionStart, session.SessionClose), // session.DialerAddr, session.ClientAddr, session.TargetAddr, byteCountSI(session.UploadBytes), byteCountSI(session.DownloadBytes), ) } _, _ = fmt.Fprintf(w, "
ProcessNetworkDateDurationClient AddrTarget AddrUploadDownload
%v%v%v%v%v%v%v%v
\n") } w := bufio.NewWriter(resp) // Html head _, _ = fmt.Fprintf(w, ` Go-tun2socks

Go-tun2socks %s

`, C.Version) // calculate traffic trafficUp := atomic.LoadInt64(&s.trafficUp) trafficDown := atomic.LoadInt64(&s.trafficDown) for _, session := range activeSessions { trafficUp += session.UploadBytes trafficDown += session.DownloadBytes } // statistics _, _ = fmt.Fprintf(w, `

Statistics (%d)

Last Refresh TimePlatform VersionCPUMEMUptimeTotalUploadDownload
%v%v%v%v%v%v%v%v
`, runtime.NumGoroutine(), date(time.Now()), platform(), cpu(), mem(), uptime(), byteCountSI(trafficUp+trafficDown), byteCountSI(trafficUp), byteCountSI(trafficDown), ) // Session table _, _ = fmt.Fprintf(w, "

Active sessions (%d)

\n", len(activeSessions)) tablePrint(w, activeSessions) _, _ = fmt.Fprintf(w, "

Closed sessions (%d)

\n", len(closedSessions)) tablePrint(w, closedSessions) _, _ = fmt.Fprintf(w, "
\n") _ = w.Flush() } func (s *Server) Start() error { _, port, err := net.SplitHostPort(s.ServeAddr) if port == "0" || port == "" || err != nil { return errors.New("address format error") } tcpAddr, err := net.ResolveTCPAddr("tcp", s.ServeAddr) if err != nil { return err } c, err := net.ListenTCP("tcp", tcpAddr) if err != nil { return err } mux := http.NewServeMux() mux.HandleFunc("/", s.serveHTML) mux.HandleFunc("/json", s.serveJSON) box := packr.New("CSSBox", "./css") mux.Handle("/css/", http.StripPrefix("/css/", http.FileServer(box))) s.Server = &http.Server{Addr: s.ServeAddr, Handler: mux} go func() { s.Serve(c) }() return nil } func (s *Server) Stop() error { return s.Close() } func (s *Server) AddSession(key interface{}, session *Session) { if session != nil { s.activeSessionMap.Store(key, session) } } func (s *Server) RemoveSession(key interface{}) { if item, ok := s.activeSessionMap.Load(key); ok { session := item.(*Session) // delete first s.activeSessionMap.Delete(key) // record up & down traffic atomic.AddInt64(&s.trafficUp, atomic.LoadInt64(&session.UploadBytes)) atomic.AddInt64(&s.trafficDown, atomic.LoadInt64(&session.DownloadBytes)) // move to closed sessions s.Lock() s.closedSessionList = append(s.closedSessionList, *session) if len(s.closedSessionList) > maxClosedSessions { s.closedSessionList = s.closedSessionList[1:] } s.Unlock() } }