FIXED:
* Better request tracking through flow

ADDED:
* Cross-check detection
This commit is contained in:
brent saner
2025-12-17 19:21:07 -05:00
parent 9f97fcaf81
commit 1a4549ea72
7 changed files with 73 additions and 40 deletions

View File

@@ -25,6 +25,8 @@ const (
falseUaFieldStr string = "No"
dfltIndent string = " "
httpRealHdr string = "X-ClientInfo-RealIP" // TODO: advise https://nginx.org/en/docs/http/ngx_http_realip_module.html NGINX module? Allow config for user-specified header?
httpCIDHdr string = "Request-Id"
urlParamXCheck string = "src_req_id"
)
var (

View File

@@ -2,20 +2,30 @@ package server
import (
`fmt`
`net/url`
)
func (p *Page) AltURL() (altUrl string) {
var u *url.URL
if !p.HasAltURL() {
return
}
if p.Info.IP.Is4() {
altUrl = p.srv.v6Url.String()
u = p.srv.v6Url
} else if p.Info.IP.Is6() {
altUrl = p.srv.v4Url.String()
u = p.srv.v4Url
} else {
// lol how did you get here
return
}
u.Query().Add(urlParamXCheck, p.ReqUUID.String())
altUrl = u.String()
return
}

View File

@@ -19,6 +19,7 @@ import (
sysd `github.com/coreos/go-systemd/daemon`
`github.com/davecgh/go-spew/spew`
`github.com/goccy/go-yaml`
`github.com/google/uuid`
`r00t2.io/clientinfo/version`
`r00t2.io/goutils/multierr`
)
@@ -261,6 +262,7 @@ func (s *Server) Reload() (err error) {
}
func (s *Server) explicit404(resp http.ResponseWriter, req *http.Request) {
s.log.Debug("server.Server.explicit404: '%s' requested '%s %s'", req.RemoteAddr, req.Method, req.URL.String())
resp.WriteHeader(http.StatusNotFound)
}
@@ -278,10 +280,12 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) {
var parsedFmts []*parsedMIME
var renderer outerRenderer
var includeFmt string
var reqId uuid.UUID = uuid.New()
var params url.Values = make(url.Values)
var outerFmt string = mediaJSON
s.log.Debug("server.Server.handleDefault: Handling request:\n%s", spew.Sdump(req))
s.log.Info("server.Server.handleDefault: '%s' (%s): '%s %s'", reqId.String(), req.RemoteAddr, req.Method, req.URL.String())
s.log.Debug("server.Server.handleDefault: Handling request for '%s':\n%s", reqId.String(), spew.Sdump(req))
origin = req.Header.Get("Origin")
if s.corsOrigins != nil && len(s.corsOrigins) != 0 && origin != "" {
if _, ok = s.corsOrigins[origin]; ok {
@@ -291,6 +295,7 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) {
}
resp.Header().Set("ClientInfo-Version", version.Ver.Short())
resp.Header().Set(httpCIDHdr, reqId.String())
page = &Page{
Info: &R00tInfo{
@@ -305,6 +310,7 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) {
RawFmt: nil,
Indent: "",
DoIndent: false,
ReqUUID: reqId,
}
// First the client info.
@@ -316,7 +322,7 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) {
}
if remAddrPort != "" {
if nAP, err = netip.ParseAddrPort(remAddrPort); err != nil {
s.log.Warning("server.Server.handleDefault: Failed to parse remote address '%s': %v", remAddrPort, err)
s.log.Warning("server.Server.handleDefault: Failed to parse remote address '%s' for '%s': %v", remAddrPort, reqId.String(), err)
// Don't return an error in case we're doing weird things like direct socket clients.
/*
http.Error(resp, "ERROR: Failed to parse client address", http.StatusInternalServerError)
@@ -329,13 +335,16 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) {
}
if req.URL != nil {
params = req.URL.Query()
if params.Has(urlParamXCheck) {
s.log.Info("server.Server.handleDefault: Cross-stack check: '%s' => '%s'", params.Get(urlParamXCheck), reqId.String())
}
}
uas = req.Header.Values("User-Agent")
if uas != nil && len(uas) > 0 {
page.Info.Client = make([]*R00tClient, 0, len(uas))
for _, ua := range uas {
if parsedUA, err = NewClient(ua); err != nil {
s.log.Err("server.Server.handleDefault: Failed to create client for '%s': %v", ua, err)
s.log.Err("server.Server.handleDefault: Failed to create client for UA '%s' via request '%s': %v", ua, reqId.String(), err)
http.Error(resp, fmt.Sprintf("ERROR: Failed to parse 'User-Agent' '%s'", ua), http.StatusInternalServerError)
return
}
@@ -363,7 +372,7 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) {
reqdMimes = req.Header.Values("Accept")
if reqdMimes != nil && len(reqdMimes) > 0 {
if parsedFmts, err = parseAccept(strings.Join(reqdMimes, ",")); err != nil {
s.log.Err("server.Server.handleDefault: Failed to parse Accept header '%#v' for '%s': %v", reqdMimes, remAddrPort, err)
s.log.Err("server.Server.handleDefault: Failed to parse Accept header '%#v' for '%s' ('%s'): %v", reqdMimes, remAddrPort, reqId.String(), err)
resp.Header()["Accept"] = okAcceptMime
http.Error(
resp,
@@ -374,12 +383,12 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) {
}
if outerFmt, err = decideParseAccept(parsedFmts, outerFmt); err != nil {
if errors.Is(err, ErrUnsupportedMIME) {
s.log.Err("server.Server.handleDefault: No supported MIME type found for '%s' via '%#v'.", remAddrPort, reqdMimes)
s.log.Err("server.Server.handleDefault: No supported MIME type found for '%s' via '%#v' for '%s'.", remAddrPort, reqdMimes, reqId.String())
resp.Header()["Accept"] = okAcceptMime
http.Error(resp, "ERROR: No supported MIME type specified via request 'Accept'; see 'Accept' header in response for valid types.", http.StatusNotAcceptable)
return
} else {
s.log.Err("server.Server.handleDefault: Received unknown error choosing from Accept header for '%s': %v", remAddrPort, err)
s.log.Err("server.Server.handleDefault: Received unknown error choosing from Accept header for '%s' from '%s': %v", remAddrPort, reqId.String(), err)
http.Error(resp, "ERROR: Unknown error occurred when negotiating MIME type.", http.StatusInternalServerError)
return
}
@@ -388,7 +397,7 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) {
// `mime` URL query parameter.
if params.Has("mime") {
if parsedFmts, err = parseAccept(strings.Join(params["mime"], ",")); err != nil {
s.log.Err("server.Server.handleDefault: Failed to parse 'mime' URL parameter '%#v' for '%s': %v", params["mime"], remAddrPort, err)
s.log.Err("server.Server.handleDefault: Failed to parse 'mime' URL parameter '%#v' for '%s' (%s): %v", params["mime"], remAddrPort, reqId.String(), err)
resp.Header()["Accept"] = okAcceptMime
http.Error(
resp,
@@ -399,12 +408,12 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) {
}
if outerFmt, err = decideParseAccept(parsedFmts, outerFmt); err != nil {
if errors.Is(err, ErrUnsupportedMIME) {
s.log.Err("server.Server.handleDefault: No supported MIME type found for '%s' via '%#v'.", remAddrPort, params["mime"])
s.log.Err("server.Server.handleDefault: No supported MIME type found for '%s' (%s) via '%#v'.", remAddrPort, reqId.String(), params["mime"])
resp.Header()["Accept"] = okAcceptMime
http.Error(resp, "ERROR: No supported MIME type specified via URL parameter 'mime'; see 'Accept' header in response for valid types.", http.StatusNotAcceptable)
return
} else {
s.log.Err("server.Server.handleDefault: Received unknown error choosing from 'mime' URL parameter for '%s': %v", remAddrPort, err)
s.log.Err("server.Server.handleDefault: Received unknown error choosing from 'mime' URL parameter for '%s' (%s): %v", remAddrPort, reqId.String(), err)
http.Error(resp, "ERROR: Unknown error occurred when negotiating MIME type.", http.StatusInternalServerError)
return
}
@@ -413,7 +422,7 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) {
// 'include' URL query parameter (only for text/html).
if outerFmt == mediaHTML && params.Has("include") {
if parsedFmts, err = parseAccept(strings.Join(params["include"], ",")); err != nil {
s.log.Err("server.Server.handleDefault: Failed to parse 'include' URL parameter '%#v' for '%s': %v", params["include"], remAddrPort, err)
s.log.Err("server.Server.handleDefault: Failed to parse 'include' URL parameter '%#v' for '%s' (%s): %v", params["include"], remAddrPort, reqId.String(), err)
resp.Header()["Accept"] = okAcceptMime
http.Error(
resp,
@@ -424,12 +433,12 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) {
}
if includeFmt, err = decideParseAccept(parsedFmts, includeFmt); err != nil {
if errors.Is(err, ErrUnsupportedMIME) {
s.log.Err("server.Server.handleDefault: No supported MIME type found for '%s' via '%#v'.", remAddrPort, params["include"])
s.log.Err("server.Server.handleDefault: No supported MIME type found for '%s' (%s) via '%#v'.", remAddrPort, reqId.String(), params["include"])
resp.Header()["Accept"] = okAcceptMime
http.Error(resp, "ERROR: No supported MIME type specified via URL parameter 'include'; see 'Accept' header in response for valid types.", http.StatusNotAcceptable)
return
} else {
s.log.Err("server.Server.handleDefault: Received unknown error choosing from 'include' URL parameter for '%s': %v", remAddrPort, err)
s.log.Err("server.Server.handleDefault: Received unknown error choosing from 'include' URL parameter for '%s' (%s): %v", remAddrPort, reqId.String(), err)
http.Error(resp, "ERROR: Unknown error occurred when negotiating MIME type.", http.StatusInternalServerError)
return
}
@@ -459,13 +468,13 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) {
case mediaYAML:
renderer = s.renderYML
default:
s.log.Err("server.Server.handleDefault: Unknown output format '%s'", outerFmt)
s.log.Err("server.Server.handleDefault: Unknown output format '%s' from '%s' (%s)", outerFmt, remAddrPort, reqId.String())
http.Error(resp, "ERROR: Unable to determine default renderer.", http.StatusInternalServerError)
return
}
if err = renderer(page, resp); err != nil {
s.log.Err("server.Server.handleDefault: Failed to render request from '%s' as '%s': %v", remAddrPort, outerFmt, err)
s.log.Err("server.Server.handleDefault: Failed to render request from '%s' (%s) as '%s': %v", remAddrPort, reqId.String(), outerFmt, err)
// The renderer handles the error-handling with the client.
return
}
@@ -481,20 +490,23 @@ func (s *Server) handleAbout(resp http.ResponseWriter, req *http.Request) {
Req: req,
},
PageType: "about",
ReqUUID: uuid.New(),
}
s.log.Debug("server.Server.handleAbout: Handling request:\n%s", spew.Sdump(req))
s.log.Info("server.Server.handleAbout: '%s' (%s)", renderPage.ReqUUID.String(), req.RemoteAddr)
s.log.Debug("server.Server.handleAbout: Handling request for '%s':\n%s", renderPage.ReqUUID.String(), spew.Sdump(req))
resp.Header().Set("ClientInfo-Version", version.Ver.Short())
resp.Header().Set("Content-Type", "text/html; charset=utf-8")
resp.Header().Set(httpCIDHdr, renderPage.ReqUUID.String())
if err = tpl.ExecuteTemplate(resp, "about", renderPage); err != nil {
s.log.Err("server.Server.handleAbout: Failed to execute template for '%s': %v", req.RemoteAddr, err)
s.log.Err("server.Server.handleAbout: Failed to execute template for '%s': %v", renderPage.ReqUUID.String(), err)
http.Error(resp, "ERROR: Failed to render HTML", http.StatusInternalServerError)
return
}
s.log.Debug("server.Server.handleAbout: Handled request:\n%s", spew.Sdump(req))
s.log.Debug("server.Server.handleAbout: Handled request for '%s'", renderPage.ReqUUID.String())
return
}
@@ -507,20 +519,23 @@ func (s *Server) handleUsage(resp http.ResponseWriter, req *http.Request) {
Req: req,
},
PageType: "usage",
ReqUUID: uuid.New(),
}
s.log.Debug("server.Server.handleUsage: Handling request:\n%s", spew.Sdump(req))
s.log.Info("server.Server.handleUsage: '%s' (%s)", renderPage.ReqUUID.String(), req.RemoteAddr)
s.log.Debug("server.Server.handleUsage: Handling request for '%s':\n%s", renderPage.ReqUUID.String(), spew.Sdump(req))
resp.Header().Set("ClientInfo-Version", version.Ver.Short())
resp.Header().Set("Content-Type", "text/html; charset=utf-8")
resp.Header().Set(httpCIDHdr, renderPage.ReqUUID.String())
if err = tpl.ExecuteTemplate(resp, "usage", renderPage); err != nil {
s.log.Err("server.Server.handleAbout: Failed to execute template for '%s': %v", req.RemoteAddr, err)
s.log.Err("server.Server.handleAbout: Failed to execute template for '%s' (%s): %v", renderPage.ReqUUID.String(), req.RemoteAddr, err)
http.Error(resp, "ERROR: Failed to render HTML", http.StatusInternalServerError)
return
}
s.log.Debug("server.Server.handleUsage: Handled request:\n%s", spew.Sdump(req))
s.log.Debug("server.Server.handleUsage: Handled request for '%s'", renderPage.ReqUUID.String())
return
}
@@ -533,7 +548,7 @@ func (s *Server) renderJSON(page *Page, resp http.ResponseWriter) (err error) {
if page.DoIndent {
if b, err = json.MarshalIndent(page.Info, "", page.Indent); err != nil {
s.log.Err("server.Server.renderJSON: Failed to render to indented JSON: %v", err)
s.log.Err("server.Server.renderJSON: Failed to render to indented JSON for '%s': %v", page.ReqUUID.String(), err)
http.Error(resp, "ERROR: Failed to render JSON", http.StatusInternalServerError)
return
}
@@ -566,13 +581,13 @@ func (s *Server) renderHTML(page *Page, resp http.ResponseWriter) (err error) {
case mediaJSON, mediaXML:
if page.DoIndent {
if b, err = mediaIndent[*page.RawFmt](page.Info, "", page.Indent); err != nil {
s.log.Err("server.Server.renderHTML: Failed to render to indented include '%s': %v", *page.RawFmt, err)
s.log.Err("server.Server.renderHTML: Failed to render to indented include '%s' for '%s': %v", *page.RawFmt, page.ReqUUID.String(), err)
http.Error(resp, "ERROR: Failed to render include format", http.StatusInternalServerError)
return
}
} else {
if b, err = mediaNoIndent[*page.RawFmt](page.Info); err != nil {
s.log.Err("server.Server.renderHTML: Failed to render to include '%s': %v", *page.RawFmt, err)
s.log.Err("server.Server.renderHTML: Failed to render to include '%s' for '%s': %v", *page.RawFmt, page.ReqUUID.String(), err)
http.Error(resp, "ERROR: Failed to render include format", http.StatusInternalServerError)
return
}
@@ -580,7 +595,9 @@ func (s *Server) renderHTML(page *Page, resp http.ResponseWriter) (err error) {
// Non-indentable
case mediaYAML:
if b, err = mediaNoIndent[*page.RawFmt](page.Info); err != nil {
s.log.Err("server.Server.renderHTML: Failed to render to '%s': %v", *page.RawFmt, err)
s.log.Err("server.Server.renderHTML: Failed to render to '%s' for '%s': %v", *page.RawFmt, page.ReqUUID.String(), err)
http.Error(resp, "ERROR: Failed to render include format", http.StatusInternalServerError)
return
}
}
if b != nil {
@@ -592,7 +609,7 @@ func (s *Server) renderHTML(page *Page, resp http.ResponseWriter) (err error) {
resp.Header().Set("Content-Type", "text/html; charset=utf-8")
if err = tpl.ExecuteTemplate(resp, "index", page); err != nil {
s.log.Err("server.Server.renderHTML: Failed to render template: %v", err)
s.log.Err("server.Server.renderHTML: Failed to render template for '%s': %v", page.ReqUUID.String(), err)
http.Error(resp, "ERROR: Failed to render HTML", http.StatusInternalServerError)
return
}
@@ -606,13 +623,13 @@ func (s *Server) renderXML(page *Page, resp http.ResponseWriter) (err error) {
if page.DoIndent {
if b, err = xml.MarshalIndent(page.Info, "", page.Indent); err != nil {
s.log.Err("server.Server.renderXML: Failed to render to indented XML: %v", err)
s.log.Err("server.Server.renderXML: Failed to render to indented XML for '%s': %v", page.ReqUUID.String(), err)
http.Error(resp, "ERROR: Failed to render XML", http.StatusInternalServerError)
return
}
} else {
if b, err = xml.Marshal(page.Info); err != nil {
s.log.Err("server.Server.renderXML: Failed to render to XML: %v", err)
s.log.Err("server.Server.renderXML: Failed to render to XML for '%s': %v", page.ReqUUID.String(), err)
http.Error(resp, "ERROR: Failed to render XML", http.StatusInternalServerError)
return
}
@@ -621,7 +638,7 @@ func (s *Server) renderXML(page *Page, resp http.ResponseWriter) (err error) {
resp.Header().Set("Content-Type", "application/xml; charset=utf-8")
if _, err = resp.Write(b); err != nil {
s.log.Err("server.Server.renderXML: Failed to send XML: %v", err)
s.log.Err("server.Server.renderXML: Failed to send XML to '%s': %v", page.ReqUUID.String(), err)
return
}
@@ -633,15 +650,15 @@ func (s *Server) renderYML(page *Page, resp http.ResponseWriter) (err error) {
var b []byte
if b, err = yaml.Marshal(page.Info); err != nil {
s.log.Err("server.Server.renderJSON: Failed to render to JSON: %v", err)
http.Error(resp, "ERROR: Failed to render JSON", http.StatusInternalServerError)
s.log.Err("server.Server.renderYML: Failed to render to YAML for '%s': %v", page.ReqUUID.String(), err)
http.Error(resp, "ERROR: Failed to render YAML", http.StatusInternalServerError)
return
}
resp.Header().Set("Content-Type", "application/yaml")
if _, err = resp.Write(b); err != nil {
s.log.Err("server.Server.renderJSON: Failed to send JSON: %v", err)
s.log.Err("server.Server.renderYML: Failed to send YAML to '%s': %v", page.ReqUUID.String(), err)
return
}

View File

@@ -2,9 +2,9 @@ package server
import (
`fmt`
`html/template`
`net/netip`
`strings`
`html/template`
)
func getIpver(a netip.Addr) (verStr string) {

View File

@@ -4,7 +4,7 @@
{{- $linkico := "🔗" }}
<h2 id="client">Client/Browser Information<a href="#client">{{ $linkico }}</a></h2>
<p>
<b>Your IP{{ getIpver $page.Info.IP }} Address is <i><a href="https://ipinfo.io/{{ $page.Info.IP.String }}">{{ $page.Info.IP.String }}</a></i>.</b>
<b>Your IP{{ getIpver $page.Info.IP }} Address is <i>{{ $page.Info.IP.String }}</i> <i>(<a href="https://ipinfo.io/{{ $page.Info.IP.String }}">more info</a></i>).</b>
<br/>
<i>You are connecting with port <b>{{ $page.Info.Port }}</b> outbound.</i>
{{- if $page.HasAltURL }}
@@ -22,9 +22,9 @@
const addr = dat.ip;
const infoDiv = document.getElementById('alt_addr');
infoDiv.innerHTML = `
<i>You also have IP{{ $page.AltVer }} address <b><a href="https://ipinfo.io/${addr}">${addr}</a></b>.</i>
<i>You also have IP{{ $page.AltVer }} address <b>${addr}</b> (<a href="https://ipinfo.io/${addr}">more info</a>).</i>
<br/>
<i>Try loading <b><a href="{{ $page.AltURL }}">{{ $page.AltURL }}</a></b> for more information.</i>`;
<i>Try loading <b><a href="{{ $page.AltURL }}">{{ $page.AltURL }}</a></b> to check your secondary address.</i>`;
}
} catch (error) {
console.info('Did not fetch alternate address: ', error);

View File

@@ -4,6 +4,7 @@
{{- $linkico := "🔗" -}}
<!DOCTYPE html>
<html lang="en">
<!-- Request ID: {{ .ReqUUID.String }} -->
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{ getTitle $page.PageType }}</title>

View File

@@ -8,6 +8,7 @@ import (
"net/url"
"os"
"github.com/google/uuid"
"github.com/mileusna/useragent"
"r00t2.io/clientinfo/args"
"r00t2.io/goutils/logging"
@@ -87,6 +88,8 @@ type Page struct {
Indent string
// DoIndent indicates if indenting was enabled.
DoIndent bool
// ReqUUID is a unique request ID assigned.
ReqUUID uuid.UUID
srv *Server
}