server.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  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/docker/docker/libnetwork/internal/caller"
  13. "github.com/docker/docker/pkg/stack"
  14. "github.com/sirupsen/logrus"
  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. logrus.Info("The server is already up and running")
  78. return
  79. }
  80. logrus.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. logrus.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. logrus.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() //nolint:errcheck
  113. _, json := 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 := logrus.WithFields(logrus.Fields{"component": "diagnostic", "remoteIP": r.RemoteAddr, "method": caller.Name(0), "url": r.URL.String()})
  117. log.Info("command not implemented done")
  118. HTTPReply(w, rsp, json) //nolint:errcheck
  119. }
  120. func help(ctx interface{}, w http.ResponseWriter, r *http.Request) {
  121. r.ParseForm() //nolint:errcheck
  122. _, json := ParseHTTPFormOptions(r)
  123. // audit logs
  124. log := logrus.WithFields(logrus.Fields{"component": "diagnostic", "remoteIP": r.RemoteAddr, "method": caller.Name(0), "url": r.URL.String()})
  125. log.Info("help done")
  126. n, ok := ctx.(*Server)
  127. var result string
  128. if ok {
  129. for path := range n.registeredHanders {
  130. result += fmt.Sprintf("%s\n", path)
  131. }
  132. HTTPReply(w, CommandSucceed(&StringCmd{Info: result}), json) //nolint:errcheck
  133. }
  134. }
  135. func ready(ctx interface{}, w http.ResponseWriter, r *http.Request) {
  136. r.ParseForm() //nolint:errcheck
  137. _, json := ParseHTTPFormOptions(r)
  138. // audit logs
  139. log := logrus.WithFields(logrus.Fields{"component": "diagnostic", "remoteIP": r.RemoteAddr, "method": caller.Name(0), "url": r.URL.String()})
  140. log.Info("ready done")
  141. HTTPReply(w, CommandSucceed(&StringCmd{Info: "OK"}), json) //nolint:errcheck
  142. }
  143. func stackTrace(ctx interface{}, w http.ResponseWriter, r *http.Request) {
  144. r.ParseForm() //nolint:errcheck
  145. _, json := ParseHTTPFormOptions(r)
  146. // audit logs
  147. log := logrus.WithFields(logrus.Fields{"component": "diagnostic", "remoteIP": r.RemoteAddr, "method": caller.Name(0), "url": r.URL.String()})
  148. log.Info("stack trace")
  149. path, err := stack.DumpToFile("/tmp/")
  150. if err != nil {
  151. log.WithError(err).Error("failed to write goroutines dump")
  152. HTTPReply(w, FailCommand(err), json) //nolint:errcheck
  153. } else {
  154. log.Info("stack trace done")
  155. HTTPReply(w, CommandSucceed(&StringCmd{Info: fmt.Sprintf("goroutine stacks written to %s", path)}), json) //nolint:errcheck
  156. }
  157. }
  158. // DebugHTTPForm helper to print the form url parameters
  159. func DebugHTTPForm(r *http.Request) {
  160. for k, v := range r.Form {
  161. logrus.Debugf("Form[%q] = %q\n", k, v)
  162. }
  163. }
  164. // JSONOutput contains details on JSON output printing
  165. type JSONOutput struct {
  166. enable bool
  167. prettyPrint bool
  168. }
  169. // ParseHTTPFormOptions easily parse the JSON printing options
  170. func ParseHTTPFormOptions(r *http.Request) (bool, *JSONOutput) {
  171. _, unsafe := r.Form["unsafe"]
  172. v, json := r.Form["json"]
  173. var pretty bool
  174. if len(v) > 0 {
  175. pretty = v[0] == "pretty"
  176. }
  177. return unsafe, &JSONOutput{enable: json, prettyPrint: pretty}
  178. }
  179. // HTTPReply helper function that takes care of sending the message out
  180. func HTTPReply(w http.ResponseWriter, r *HTTPResult, j *JSONOutput) (int, error) {
  181. var response []byte
  182. if j.enable {
  183. w.Header().Set("Content-Type", "application/json")
  184. var err error
  185. if j.prettyPrint {
  186. response, err = json.MarshalIndent(r, "", " ")
  187. if err != nil {
  188. response, _ = json.MarshalIndent(FailCommand(err), "", " ")
  189. }
  190. } else {
  191. response, err = json.Marshal(r)
  192. if err != nil {
  193. response, _ = json.Marshal(FailCommand(err))
  194. }
  195. }
  196. } else {
  197. response = []byte(r.String())
  198. }
  199. return fmt.Fprint(w, string(response))
  200. }