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