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 }