github.com/rish1988/moby@v25.0.2+incompatible/libnetwork/diagnostic/server.go (about)

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