github.com/pwn-term/docker@v0.0.0-20210616085119-6e977cce2565/libnetwork/diagnostic/server.go (about)

     1  package diagnostic
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"net/http"
     8  	"sync"
     9  	"sync/atomic"
    10  
    11  	stackdump "github.com/docker/docker/pkg/signal"
    12  	"github.com/docker/libnetwork/internal/caller"
    13  	"github.com/sirupsen/logrus"
    14  )
    15  
    16  // HTTPHandlerFunc TODO
    17  type HTTPHandlerFunc func(interface{}, http.ResponseWriter, *http.Request)
    18  
    19  type httpHandlerCustom struct {
    20  	ctx interface{}
    21  	F   func(interface{}, http.ResponseWriter, *http.Request)
    22  }
    23  
    24  // ServeHTTP TODO
    25  func (h httpHandlerCustom) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    26  	h.F(h.ctx, w, r)
    27  }
    28  
    29  var diagPaths2Func = map[string]HTTPHandlerFunc{
    30  	"/":          notImplemented,
    31  	"/help":      help,
    32  	"/ready":     ready,
    33  	"/stackdump": stackTrace,
    34  }
    35  
    36  // Server when the debug is enabled exposes a
    37  // This data structure is protected by the Agent mutex so does not require and additional mutex here
    38  type Server struct {
    39  	enable            int32
    40  	srv               *http.Server
    41  	port              int
    42  	mux               *http.ServeMux
    43  	registeredHanders map[string]bool
    44  	sync.Mutex
    45  }
    46  
    47  // New creates a new diagnostic server
    48  func New() *Server {
    49  	return &Server{
    50  		registeredHanders: make(map[string]bool),
    51  	}
    52  }
    53  
    54  // Init initialize the mux for the http handling and register the base hooks
    55  func (s *Server) Init() {
    56  	s.mux = http.NewServeMux()
    57  
    58  	// Register local handlers
    59  	s.RegisterHandler(s, diagPaths2Func)
    60  }
    61  
    62  // RegisterHandler allows to register new handlers to the mux and to a specific path
    63  func (s *Server) RegisterHandler(ctx interface{}, hdlrs map[string]HTTPHandlerFunc) {
    64  	s.Lock()
    65  	defer s.Unlock()
    66  	for path, fun := range hdlrs {
    67  		if _, ok := s.registeredHanders[path]; ok {
    68  			continue
    69  		}
    70  		s.mux.Handle(path, httpHandlerCustom{ctx, fun})
    71  		s.registeredHanders[path] = true
    72  	}
    73  }
    74  
    75  // ServeHTTP this is the method called bu the ListenAndServe, and is needed to allow us to
    76  // use our custom mux
    77  func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    78  	s.mux.ServeHTTP(w, r)
    79  }
    80  
    81  // EnableDiagnostic opens a TCP socket to debug the passed network DB
    82  func (s *Server) EnableDiagnostic(ip string, port int) {
    83  	s.Lock()
    84  	defer s.Unlock()
    85  
    86  	s.port = port
    87  
    88  	if s.enable == 1 {
    89  		logrus.Info("The server is already up and running")
    90  		return
    91  	}
    92  
    93  	logrus.Infof("Starting the diagnostic server listening on %d for commands", port)
    94  	srv := &http.Server{Addr: fmt.Sprintf("%s:%d", ip, port), Handler: s}
    95  	s.srv = srv
    96  	s.enable = 1
    97  	go func(n *Server) {
    98  		// Ignore ErrServerClosed that is returned on the Shutdown call
    99  		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
   100  			logrus.Errorf("ListenAndServe error: %s", err)
   101  			atomic.SwapInt32(&n.enable, 0)
   102  		}
   103  	}(s)
   104  }
   105  
   106  // DisableDiagnostic stop the dubug and closes the tcp socket
   107  func (s *Server) DisableDiagnostic() {
   108  	s.Lock()
   109  	defer s.Unlock()
   110  
   111  	s.srv.Shutdown(context.Background())
   112  	s.srv = nil
   113  	s.enable = 0
   114  	logrus.Info("Disabling the diagnostic server")
   115  }
   116  
   117  // IsDiagnosticEnabled returns true when the debug is enabled
   118  func (s *Server) IsDiagnosticEnabled() bool {
   119  	s.Lock()
   120  	defer s.Unlock()
   121  	return s.enable == 1
   122  }
   123  
   124  func notImplemented(ctx interface{}, w http.ResponseWriter, r *http.Request) {
   125  	r.ParseForm()
   126  	_, json := ParseHTTPFormOptions(r)
   127  	rsp := WrongCommand("not implemented", fmt.Sprintf("URL path: %s no method implemented check /help\n", r.URL.Path))
   128  
   129  	// audit logs
   130  	log := logrus.WithFields(logrus.Fields{"component": "diagnostic", "remoteIP": r.RemoteAddr, "method": caller.Name(0), "url": r.URL.String()})
   131  	log.Info("command not implemented done")
   132  
   133  	HTTPReply(w, rsp, json)
   134  }
   135  
   136  func help(ctx interface{}, w http.ResponseWriter, r *http.Request) {
   137  	r.ParseForm()
   138  	_, json := ParseHTTPFormOptions(r)
   139  
   140  	// audit logs
   141  	log := logrus.WithFields(logrus.Fields{"component": "diagnostic", "remoteIP": r.RemoteAddr, "method": caller.Name(0), "url": r.URL.String()})
   142  	log.Info("help done")
   143  
   144  	n, ok := ctx.(*Server)
   145  	var result string
   146  	if ok {
   147  		for path := range n.registeredHanders {
   148  			result += fmt.Sprintf("%s\n", path)
   149  		}
   150  		HTTPReply(w, CommandSucceed(&StringCmd{Info: result}), json)
   151  	}
   152  }
   153  
   154  func ready(ctx interface{}, w http.ResponseWriter, r *http.Request) {
   155  	r.ParseForm()
   156  	_, json := ParseHTTPFormOptions(r)
   157  
   158  	// audit logs
   159  	log := logrus.WithFields(logrus.Fields{"component": "diagnostic", "remoteIP": r.RemoteAddr, "method": caller.Name(0), "url": r.URL.String()})
   160  	log.Info("ready done")
   161  	HTTPReply(w, CommandSucceed(&StringCmd{Info: "OK"}), json)
   162  }
   163  
   164  func stackTrace(ctx interface{}, w http.ResponseWriter, r *http.Request) {
   165  	r.ParseForm()
   166  	_, json := ParseHTTPFormOptions(r)
   167  
   168  	// audit logs
   169  	log := logrus.WithFields(logrus.Fields{"component": "diagnostic", "remoteIP": r.RemoteAddr, "method": caller.Name(0), "url": r.URL.String()})
   170  	log.Info("stack trace")
   171  
   172  	path, err := stackdump.DumpStacks("/tmp/")
   173  	if err != nil {
   174  		log.WithError(err).Error("failed to write goroutines dump")
   175  		HTTPReply(w, FailCommand(err), json)
   176  	} else {
   177  		log.Info("stack trace done")
   178  		HTTPReply(w, CommandSucceed(&StringCmd{Info: fmt.Sprintf("goroutine stacks written to %s", path)}), json)
   179  	}
   180  }
   181  
   182  // DebugHTTPForm helper to print the form url parameters
   183  func DebugHTTPForm(r *http.Request) {
   184  	for k, v := range r.Form {
   185  		logrus.Debugf("Form[%q] = %q\n", k, v)
   186  	}
   187  }
   188  
   189  // JSONOutput contains details on JSON output printing
   190  type JSONOutput struct {
   191  	enable      bool
   192  	prettyPrint bool
   193  }
   194  
   195  // ParseHTTPFormOptions easily parse the JSON printing options
   196  func ParseHTTPFormOptions(r *http.Request) (bool, *JSONOutput) {
   197  	_, unsafe := r.Form["unsafe"]
   198  	v, json := r.Form["json"]
   199  	var pretty bool
   200  	if len(v) > 0 {
   201  		pretty = v[0] == "pretty"
   202  	}
   203  	return unsafe, &JSONOutput{enable: json, prettyPrint: pretty}
   204  }
   205  
   206  // HTTPReply helper function that takes care of sending the message out
   207  func HTTPReply(w http.ResponseWriter, r *HTTPResult, j *JSONOutput) (int, error) {
   208  	var response []byte
   209  	if j.enable {
   210  		w.Header().Set("Content-Type", "application/json")
   211  		var err error
   212  		if j.prettyPrint {
   213  			response, err = json.MarshalIndent(r, "", "  ")
   214  			if err != nil {
   215  				response, _ = json.MarshalIndent(FailCommand(err), "", "  ")
   216  			}
   217  		} else {
   218  			response, err = json.Marshal(r)
   219  			if err != nil {
   220  				response, _ = json.Marshal(FailCommand(err))
   221  			}
   222  		}
   223  	} else {
   224  		response = []byte(r.String())
   225  	}
   226  	return fmt.Fprint(w, string(response))
   227  }