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