server.go 6.6 KB

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