github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/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/Prakhar-Agarwal-byte/moby/libnetwork/internal/caller" 16 "github.com/Prakhar-Agarwal-byte/moby/pkg/stack" 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 log.G(context.TODO()).Info("The server is already up and running") 93 return 94 } 95 96 log.G(context.TODO()).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 log.G(context.TODO()).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 log.G(context.TODO()).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() 133 _, jsonOutput := 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.G(context.TODO()).WithFields(log.Fields{ 138 "component": "diagnostic", 139 "remoteIP": r.RemoteAddr, 140 "method": caller.Name(0), 141 "url": r.URL.String(), 142 }).Info("command not implemented done") 143 144 _, _ = HTTPReply(w, rsp, jsonOutput) 145 } 146 147 func help(ctx interface{}, w http.ResponseWriter, r *http.Request) { 148 _ = r.ParseForm() 149 _, jsonOutput := ParseHTTPFormOptions(r) 150 151 // audit logs 152 log.G(context.TODO()).WithFields(log.Fields{ 153 "component": "diagnostic", 154 "remoteIP": r.RemoteAddr, 155 "method": caller.Name(0), 156 "url": r.URL.String(), 157 }).Info("help done") 158 159 n, ok := ctx.(*Server) 160 var result string 161 if ok { 162 for path := range n.registeredHanders { 163 result += fmt.Sprintf("%s\n", path) 164 } 165 _, _ = HTTPReply(w, CommandSucceed(&StringCmd{Info: result}), jsonOutput) 166 } 167 } 168 169 func ready(ctx interface{}, w http.ResponseWriter, r *http.Request) { 170 _ = r.ParseForm() 171 _, jsonOutput := ParseHTTPFormOptions(r) 172 173 // audit logs 174 log.G(context.TODO()).WithFields(log.Fields{ 175 "component": "diagnostic", 176 "remoteIP": r.RemoteAddr, 177 "method": caller.Name(0), 178 "url": r.URL.String(), 179 }).Info("ready done") 180 _, _ = HTTPReply(w, CommandSucceed(&StringCmd{Info: "OK"}), jsonOutput) 181 } 182 183 func stackTrace(ctx interface{}, w http.ResponseWriter, r *http.Request) { 184 _ = r.ParseForm() 185 _, jsonOutput := ParseHTTPFormOptions(r) 186 187 // audit logs 188 logger := log.G(context.TODO()).WithFields(log.Fields{"component": "diagnostic", "remoteIP": r.RemoteAddr, "method": caller.Name(0), "url": r.URL.String()}) 189 logger.Info("stack trace") 190 191 path, err := stack.DumpToFile("/tmp/") 192 if err != nil { 193 logger.WithError(err).Error("failed to write goroutines dump") 194 _, _ = HTTPReply(w, FailCommand(err), jsonOutput) 195 } else { 196 logger.Info("stack trace done") 197 _, _ = HTTPReply(w, CommandSucceed(&StringCmd{Info: "goroutine stacks written to " + path}), jsonOutput) 198 } 199 } 200 201 // DebugHTTPForm helper to print the form url parameters 202 func DebugHTTPForm(r *http.Request) { 203 for k, v := range r.Form { 204 log.G(context.TODO()).Debugf("Form[%q] = %q\n", k, v) 205 } 206 } 207 208 // JSONOutput contains details on JSON output printing 209 type JSONOutput struct { 210 enable bool 211 prettyPrint bool 212 } 213 214 // ParseHTTPFormOptions easily parse the JSON printing options 215 func ParseHTTPFormOptions(r *http.Request) (bool, *JSONOutput) { 216 _, unsafe := r.Form["unsafe"] 217 v, enableJSON := r.Form["json"] 218 var pretty bool 219 if len(v) > 0 { 220 pretty = v[0] == "pretty" 221 } 222 return unsafe, &JSONOutput{enable: enableJSON, prettyPrint: pretty} 223 } 224 225 // HTTPReply helper function that takes care of sending the message out 226 func HTTPReply(w http.ResponseWriter, r *HTTPResult, j *JSONOutput) (int, error) { 227 var response []byte 228 if j.enable { 229 w.Header().Set("Content-Type", "application/json") 230 var err error 231 if j.prettyPrint { 232 response, err = json.MarshalIndent(r, "", " ") 233 if err != nil { 234 response, _ = json.MarshalIndent(FailCommand(err), "", " ") 235 } 236 } else { 237 response, err = json.Marshal(r) 238 if err != nil { 239 response, _ = json.Marshal(FailCommand(err)) 240 } 241 } 242 } else { 243 response = []byte(r.String()) 244 } 245 return fmt.Fprint(w, string(response)) 246 }