123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246 |
- package diagnostic
- import (
- "context"
- "encoding/json"
- "fmt"
- "net"
- "net/http"
- "strconv"
- "sync"
- "sync/atomic"
- "time"
- "github.com/containerd/log"
- "github.com/docker/docker/libnetwork/internal/caller"
- "github.com/docker/docker/pkg/stack"
- )
- // HTTPHandlerFunc TODO
- type HTTPHandlerFunc func(interface{}, http.ResponseWriter, *http.Request)
- type httpHandlerCustom struct {
- ctx interface{}
- F func(interface{}, http.ResponseWriter, *http.Request)
- }
- // ServeHTTP TODO
- func (h httpHandlerCustom) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- h.F(h.ctx, w, r)
- }
- var diagPaths2Func = map[string]HTTPHandlerFunc{
- "/": notImplemented,
- "/help": help,
- "/ready": ready,
- "/stackdump": stackTrace,
- }
- // Server when the debug is enabled exposes a
- // This data structure is protected by the Agent mutex so does not require and additional mutex here
- type Server struct {
- enable int32
- srv *http.Server
- port int
- mux *http.ServeMux
- registeredHanders map[string]bool
- sync.Mutex
- }
- // New creates a new diagnostic server
- func New() *Server {
- return &Server{
- registeredHanders: make(map[string]bool),
- }
- }
- // Init initialize the mux for the http handling and register the base hooks
- func (s *Server) Init() {
- s.mux = http.NewServeMux()
- // Register local handlers
- s.RegisterHandler(s, diagPaths2Func)
- }
- // RegisterHandler allows to register new handlers to the mux and to a specific path
- func (s *Server) RegisterHandler(ctx interface{}, hdlrs map[string]HTTPHandlerFunc) {
- s.Lock()
- defer s.Unlock()
- for path, fun := range hdlrs {
- if _, ok := s.registeredHanders[path]; ok {
- continue
- }
- s.mux.Handle(path, httpHandlerCustom{ctx, fun})
- s.registeredHanders[path] = true
- }
- }
- // ServeHTTP this is the method called bu the ListenAndServe, and is needed to allow us to
- // use our custom mux
- func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- s.mux.ServeHTTP(w, r)
- }
- // EnableDiagnostic opens a TCP socket to debug the passed network DB
- func (s *Server) EnableDiagnostic(ip string, port int) {
- s.Lock()
- defer s.Unlock()
- s.port = port
- if s.enable == 1 {
- log.G(context.TODO()).Info("The server is already up and running")
- return
- }
- log.G(context.TODO()).Infof("Starting the diagnostic server listening on %d for commands", port)
- srv := &http.Server{
- Addr: net.JoinHostPort(ip, strconv.Itoa(port)),
- Handler: s,
- ReadHeaderTimeout: 5 * time.Minute, // "G112: Potential Slowloris Attack (gosec)"; not a real concern for our use, so setting a long timeout.
- }
- s.srv = srv
- s.enable = 1
- go func(n *Server) {
- // Ignore ErrServerClosed that is returned on the Shutdown call
- if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
- log.G(context.TODO()).Errorf("ListenAndServe error: %s", err)
- atomic.SwapInt32(&n.enable, 0)
- }
- }(s)
- }
- // DisableDiagnostic stop the dubug and closes the tcp socket
- func (s *Server) DisableDiagnostic() {
- s.Lock()
- defer s.Unlock()
- s.srv.Shutdown(context.Background()) //nolint:errcheck
- s.srv = nil
- s.enable = 0
- log.G(context.TODO()).Info("Disabling the diagnostic server")
- }
- // IsDiagnosticEnabled returns true when the debug is enabled
- func (s *Server) IsDiagnosticEnabled() bool {
- s.Lock()
- defer s.Unlock()
- return s.enable == 1
- }
- func notImplemented(ctx interface{}, w http.ResponseWriter, r *http.Request) {
- _ = r.ParseForm()
- _, jsonOutput := ParseHTTPFormOptions(r)
- rsp := WrongCommand("not implemented", fmt.Sprintf("URL path: %s no method implemented check /help\n", r.URL.Path))
- // audit logs
- log.G(context.TODO()).WithFields(log.Fields{
- "component": "diagnostic",
- "remoteIP": r.RemoteAddr,
- "method": caller.Name(0),
- "url": r.URL.String(),
- }).Info("command not implemented done")
- _, _ = HTTPReply(w, rsp, jsonOutput)
- }
- func help(ctx interface{}, w http.ResponseWriter, r *http.Request) {
- _ = r.ParseForm()
- _, jsonOutput := ParseHTTPFormOptions(r)
- // audit logs
- log.G(context.TODO()).WithFields(log.Fields{
- "component": "diagnostic",
- "remoteIP": r.RemoteAddr,
- "method": caller.Name(0),
- "url": r.URL.String(),
- }).Info("help done")
- n, ok := ctx.(*Server)
- var result string
- if ok {
- for path := range n.registeredHanders {
- result += fmt.Sprintf("%s\n", path)
- }
- _, _ = HTTPReply(w, CommandSucceed(&StringCmd{Info: result}), jsonOutput)
- }
- }
- func ready(ctx interface{}, w http.ResponseWriter, r *http.Request) {
- _ = r.ParseForm()
- _, jsonOutput := ParseHTTPFormOptions(r)
- // audit logs
- log.G(context.TODO()).WithFields(log.Fields{
- "component": "diagnostic",
- "remoteIP": r.RemoteAddr,
- "method": caller.Name(0),
- "url": r.URL.String(),
- }).Info("ready done")
- _, _ = HTTPReply(w, CommandSucceed(&StringCmd{Info: "OK"}), jsonOutput)
- }
- func stackTrace(ctx interface{}, w http.ResponseWriter, r *http.Request) {
- _ = r.ParseForm()
- _, jsonOutput := ParseHTTPFormOptions(r)
- // audit logs
- logger := log.G(context.TODO()).WithFields(log.Fields{"component": "diagnostic", "remoteIP": r.RemoteAddr, "method": caller.Name(0), "url": r.URL.String()})
- logger.Info("stack trace")
- path, err := stack.DumpToFile("/tmp/")
- if err != nil {
- logger.WithError(err).Error("failed to write goroutines dump")
- _, _ = HTTPReply(w, FailCommand(err), jsonOutput)
- } else {
- logger.Info("stack trace done")
- _, _ = HTTPReply(w, CommandSucceed(&StringCmd{Info: "goroutine stacks written to " + path}), jsonOutput)
- }
- }
- // DebugHTTPForm helper to print the form url parameters
- func DebugHTTPForm(r *http.Request) {
- for k, v := range r.Form {
- log.G(context.TODO()).Debugf("Form[%q] = %q\n", k, v)
- }
- }
- // JSONOutput contains details on JSON output printing
- type JSONOutput struct {
- enable bool
- prettyPrint bool
- }
- // ParseHTTPFormOptions easily parse the JSON printing options
- func ParseHTTPFormOptions(r *http.Request) (bool, *JSONOutput) {
- _, unsafe := r.Form["unsafe"]
- v, enableJSON := r.Form["json"]
- var pretty bool
- if len(v) > 0 {
- pretty = v[0] == "pretty"
- }
- return unsafe, &JSONOutput{enable: enableJSON, prettyPrint: pretty}
- }
- // HTTPReply helper function that takes care of sending the message out
- func HTTPReply(w http.ResponseWriter, r *HTTPResult, j *JSONOutput) (int, error) {
- var response []byte
- if j.enable {
- w.Header().Set("Content-Type", "application/json")
- var err error
- if j.prettyPrint {
- response, err = json.MarshalIndent(r, "", " ")
- if err != nil {
- response, _ = json.MarshalIndent(FailCommand(err), "", " ")
- }
- } else {
- response, err = json.Marshal(r)
- if err != nil {
- response, _ = json.Marshal(FailCommand(err))
- }
- }
- } else {
- response = []byte(r.String())
- }
- return fmt.Fprint(w, string(response))
- }
|