mirror of
https://github.com/SagerNet/sing-box.git
synced 2024-11-23 17:11:29 +00:00
148 lines
3.7 KiB
Go
148 lines
3.7 KiB
Go
package daemon
|
|
|
|
import (
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"net/http/pprof"
|
|
"strings"
|
|
|
|
"github.com/sagernet/sing-box/common/json"
|
|
"github.com/sagernet/sing-box/log"
|
|
"github.com/sagernet/sing-box/option"
|
|
"github.com/sagernet/sing/common"
|
|
E "github.com/sagernet/sing/common/exceptions"
|
|
F "github.com/sagernet/sing/common/format"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/go-chi/cors"
|
|
"github.com/go-chi/render"
|
|
"github.com/gorilla/websocket"
|
|
)
|
|
|
|
type Options struct {
|
|
Listen string `json:"listen"`
|
|
ListenPort uint16 `json:"listen_port"`
|
|
Secret string `json:"secret"`
|
|
WorkingDirectory string `json:"working_directory"`
|
|
}
|
|
|
|
type Server struct {
|
|
options Options
|
|
httpServer *http.Server
|
|
instance Instance
|
|
}
|
|
|
|
func NewServer(options Options) *Server {
|
|
return &Server{
|
|
options: options,
|
|
}
|
|
}
|
|
|
|
func (s *Server) Start() error {
|
|
tcpConn, err := net.Listen("tcp", net.JoinHostPort(s.options.Listen, F.ToString(s.options.ListenPort)))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
router := chi.NewRouter()
|
|
router.Use(cors.New(cors.Options{
|
|
AllowedOrigins: []string{"*"},
|
|
AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"},
|
|
AllowedHeaders: []string{"Content-Type", "Authorization"},
|
|
MaxAge: 300,
|
|
}).Handler)
|
|
if s.options.Secret != "" {
|
|
router.Use(s.authentication)
|
|
}
|
|
router.Get("/ping", s.ping)
|
|
router.Get("/status", s.status)
|
|
router.Post("/run", s.run)
|
|
router.Get("/stop", s.stop)
|
|
router.Route("/debug/pprof", func(r chi.Router) {
|
|
r.HandleFunc("/", pprof.Index)
|
|
r.HandleFunc("/cmdline", pprof.Cmdline)
|
|
r.HandleFunc("/profile", pprof.Profile)
|
|
r.HandleFunc("/symbol", pprof.Symbol)
|
|
r.HandleFunc("/trace", pprof.Trace)
|
|
})
|
|
httpServer := &http.Server{
|
|
Handler: router,
|
|
}
|
|
go httpServer.Serve(tcpConn)
|
|
s.httpServer = httpServer
|
|
return nil
|
|
}
|
|
|
|
func (s *Server) authentication(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
|
if websocket.IsWebSocketUpgrade(request) && request.URL.Query().Get("token") != "" {
|
|
token := request.URL.Query().Get("token")
|
|
if token != s.options.Secret {
|
|
render.Status(request, http.StatusUnauthorized)
|
|
return
|
|
}
|
|
next.ServeHTTP(writer, request)
|
|
return
|
|
}
|
|
header := request.Header.Get("Authorization")
|
|
bearer, token, found := strings.Cut(header, " ")
|
|
hasInvalidHeader := bearer != "Bearer"
|
|
hasInvalidSecret := !found || token != s.options.Secret
|
|
if hasInvalidHeader || hasInvalidSecret {
|
|
render.Status(request, http.StatusUnauthorized)
|
|
return
|
|
}
|
|
next.ServeHTTP(writer, request)
|
|
})
|
|
}
|
|
|
|
func (s *Server) Close() error {
|
|
return common.Close(
|
|
common.PtrOrNil(s.httpServer),
|
|
&s.instance,
|
|
)
|
|
}
|
|
|
|
func (s *Server) ping(writer http.ResponseWriter, request *http.Request) {
|
|
render.PlainText(writer, request, "pong")
|
|
}
|
|
|
|
type StatusResponse struct {
|
|
Running bool `json:"running"`
|
|
}
|
|
|
|
func (s *Server) status(writer http.ResponseWriter, request *http.Request) {
|
|
render.JSON(writer, request, StatusResponse{
|
|
Running: s.instance.Running(),
|
|
})
|
|
}
|
|
|
|
func (s *Server) run(writer http.ResponseWriter, request *http.Request) {
|
|
err := s.run0(request)
|
|
if err != nil {
|
|
log.Warn(err)
|
|
render.Status(request, http.StatusBadRequest)
|
|
render.PlainText(writer, request, err.Error())
|
|
return
|
|
}
|
|
writer.WriteHeader(http.StatusNoContent)
|
|
}
|
|
|
|
func (s *Server) run0(request *http.Request) error {
|
|
configContent, err := io.ReadAll(request.Body)
|
|
if err != nil {
|
|
return E.Cause(err, "read config")
|
|
}
|
|
var options option.Options
|
|
err = json.Unmarshal(configContent, &options)
|
|
if err != nil {
|
|
return E.Cause(err, "decode config")
|
|
}
|
|
return s.instance.Start(options)
|
|
}
|
|
|
|
func (s *Server) stop(writer http.ResponseWriter, request *http.Request) {
|
|
s.instance.Close()
|
|
writer.WriteHeader(http.StatusNoContent)
|
|
}
|