v0.2.0
ADDED: * The ability to show both IPv4 and IPv6 addresses (if the client has dual-stack and either the server does as well or a separate ClientInfo is running on the "other" net family).
This commit is contained in:
@@ -24,7 +24,7 @@ const (
|
||||
trueUaFieldStr string = "Yes"
|
||||
falseUaFieldStr string = "No"
|
||||
dfltIndent string = " "
|
||||
httpRealHdr string = "X-ClientInfo-RealIP"
|
||||
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?
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -35,6 +35,8 @@ var (
|
||||
Funcs(
|
||||
template.FuncMap{
|
||||
"getTitle": getTitle,
|
||||
"getIpver": getIpver,
|
||||
"safeUrl": safeUrl,
|
||||
},
|
||||
).ParseFS(tplDir, "tpl/*.tpl"),
|
||||
)
|
||||
|
||||
@@ -46,6 +46,7 @@ func NewClient(uaStr string) (r *R00tClient, err error) {
|
||||
func NewServer(log logging.Logger, cliArgs *args.Args) (srv *Server, err error) {
|
||||
|
||||
var s Server
|
||||
var origin string
|
||||
var udsSockPerms args.UdsPerms
|
||||
|
||||
if log == nil {
|
||||
@@ -62,6 +63,7 @@ func NewServer(log logging.Logger, cliArgs *args.Args) (srv *Server, err error)
|
||||
args: cliArgs,
|
||||
mux: http.NewServeMux(),
|
||||
sock: nil,
|
||||
corsOrigins: nil,
|
||||
reloadChan: make(chan os.Signal),
|
||||
stopChan: make(chan os.Signal),
|
||||
}
|
||||
@@ -73,6 +75,42 @@ func NewServer(log logging.Logger, cliArgs *args.Args) (srv *Server, err error)
|
||||
s.mux.HandleFunc("/usage.html", s.handleUsage)
|
||||
s.mux.HandleFunc("/favicon.ico", s.explicit404)
|
||||
|
||||
if cliArgs.CORS != nil && len(cliArgs.CORS) > 0 {
|
||||
if s.corsOrigins == nil {
|
||||
s.corsOrigins = make(map[string]struct{})
|
||||
}
|
||||
for _, origin = range cliArgs.CORS {
|
||||
s.corsOrigins[strings.ToLower(origin)] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
if cliArgs.V4Url != nil {
|
||||
if s.v4Url, err = url.Parse(*cliArgs.V4Url); err != nil {
|
||||
s.log.Err("server.NewServer: Failed to parse IPv4 URI '%s': %v", *cliArgs.V4Url, err)
|
||||
return
|
||||
}
|
||||
if s.corsOrigins == nil {
|
||||
s.corsOrigins = make(map[string]struct{})
|
||||
}
|
||||
origin = strings.ToLower(fmt.Sprintf("%s://%s", s.v4Url.Scheme, s.v4Url.Host))
|
||||
s.corsOrigins[origin] = struct{}{}
|
||||
}
|
||||
if cliArgs.V6Url != nil {
|
||||
if s.v6Url, err = url.Parse(*cliArgs.V6Url); err != nil {
|
||||
s.log.Err("server.NewServer: Failed to parse IPv6 URI '%s': %v", *cliArgs.V6Url, err)
|
||||
return
|
||||
}
|
||||
if s.corsOrigins == nil {
|
||||
s.corsOrigins = make(map[string]struct{})
|
||||
}
|
||||
origin = strings.ToLower(fmt.Sprintf("%s://%s", s.v6Url.Scheme, s.v6Url.Host))
|
||||
s.corsOrigins[origin] = struct{}{}
|
||||
}
|
||||
|
||||
if s.corsOrigins != nil {
|
||||
s.log.Debug("server.NewServer: CORS origins: %#v", s.corsOrigins)
|
||||
}
|
||||
|
||||
if s.listenUri, err = url.Parse(cliArgs.Listen.Listen); err != nil {
|
||||
s.log.Err("server.NewServer: Failed to parse listener URI: %v", err)
|
||||
return
|
||||
|
||||
@@ -4,6 +4,48 @@ import (
|
||||
`fmt`
|
||||
)
|
||||
|
||||
func (p *Page) AltURL() (altUrl string) {
|
||||
|
||||
if !p.HasAltURL() {
|
||||
return
|
||||
}
|
||||
|
||||
if p.Info.IP.Is4() {
|
||||
altUrl = p.srv.v6Url.String()
|
||||
} else if p.Info.IP.Is6() {
|
||||
altUrl = p.srv.v4Url.String()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Page) AltVer() (altVer string) {
|
||||
|
||||
if !p.HasAltURL() {
|
||||
return
|
||||
}
|
||||
|
||||
if p.Info.IP.Is4() {
|
||||
altVer = "v6"
|
||||
} else if p.Info.IP.Is6() {
|
||||
altVer = "v4"
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Page) HasAltURL() (hasAlt bool) {
|
||||
|
||||
if p.Info == nil {
|
||||
return
|
||||
}
|
||||
|
||||
hasAlt = (p.Info.IP.Is4() && p.srv.v6Url != nil) ||
|
||||
(p.Info.IP.Is6() && p.srv.v4Url != nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Page) RenderIP(indent uint) (s string) {
|
||||
|
||||
s = fmt.Sprintf("<a href=\"https://ipinfo.io/%s\">%s</a>", p.Info.IP.String(), p.Info.IP.String())
|
||||
|
||||
@@ -3,24 +3,24 @@ package server
|
||||
import (
|
||||
`encoding/json`
|
||||
`encoding/xml`
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
`errors`
|
||||
`fmt`
|
||||
`net`
|
||||
`net/http`
|
||||
`net/http/fcgi`
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"sync"
|
||||
`net/netip`
|
||||
`net/url`
|
||||
`os`
|
||||
`os/signal`
|
||||
`strings`
|
||||
`sync`
|
||||
`syscall`
|
||||
|
||||
sysd "github.com/coreos/go-systemd/daemon"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
sysd `github.com/coreos/go-systemd/daemon`
|
||||
`github.com/davecgh/go-spew/spew`
|
||||
`github.com/goccy/go-yaml`
|
||||
`r00t2.io/clientinfo/version`
|
||||
"r00t2.io/goutils/multierr"
|
||||
`r00t2.io/goutils/multierr`
|
||||
)
|
||||
|
||||
// Close cleanly closes any remnants of a Server. Stop should be used instead to cleanly shut down; this is a little more aggressive.
|
||||
@@ -267,8 +267,10 @@ func (s *Server) explicit404(resp http.ResponseWriter, req *http.Request) {
|
||||
func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) {
|
||||
|
||||
var err error
|
||||
var ok bool
|
||||
var page *Page
|
||||
var uas []string
|
||||
var origin string
|
||||
var reqdMimes []string
|
||||
var parsedUA *R00tClient
|
||||
var nAP netip.AddrPort
|
||||
@@ -280,13 +282,20 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) {
|
||||
var outerFmt string = mediaJSON
|
||||
|
||||
s.log.Debug("server.Server.handleDefault: Handling request:\n%s", spew.Sdump(req))
|
||||
origin = req.Header.Get("Origin")
|
||||
if s.corsOrigins != nil && len(s.corsOrigins) != 0 && origin != "" {
|
||||
if _, ok = s.corsOrigins[origin]; ok {
|
||||
resp.Header().Set("Access-Control-Allow-Origin", origin)
|
||||
resp.Header().Add("Vary", "Origin")
|
||||
}
|
||||
}
|
||||
|
||||
resp.Header().Set("ClientInfo-Version", version.Ver.Short())
|
||||
|
||||
page = &Page{
|
||||
Info: &R00tInfo{
|
||||
Client: nil,
|
||||
IP: nil,
|
||||
IP: netip.Addr{},
|
||||
Port: 0,
|
||||
Headers: XmlHeaders(req.Header),
|
||||
Req: req,
|
||||
@@ -301,7 +310,7 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) {
|
||||
// First the client info.
|
||||
remAddrPort = req.RemoteAddr
|
||||
if s.isHttp && req.Header.Get(httpRealHdr) != "" {
|
||||
// TODO: WHitelist explicit reverse proxy addr(s)?
|
||||
// TODO: Whitelist explicit reverse proxy addr(s)?
|
||||
remAddrPort = req.Header.Get(httpRealHdr)
|
||||
req.Header.Del(httpRealHdr)
|
||||
}
|
||||
@@ -315,7 +324,7 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) {
|
||||
*/
|
||||
err = nil
|
||||
}
|
||||
page.Info.IP = net.ParseIP(nAP.Addr().String())
|
||||
page.Info.IP = nAP.Addr().Unmap()
|
||||
page.Info.Port = nAP.Port()
|
||||
}
|
||||
if req.URL != nil {
|
||||
@@ -547,6 +556,8 @@ func (s *Server) renderHTML(page *Page, resp http.ResponseWriter) (err error) {
|
||||
|
||||
var b []byte
|
||||
|
||||
page.srv = s
|
||||
|
||||
if page.RawFmt != nil {
|
||||
switch *page.RawFmt {
|
||||
case mediaHTML:
|
||||
|
||||
@@ -2,9 +2,22 @@ package server
|
||||
|
||||
import (
|
||||
`fmt`
|
||||
`net/netip`
|
||||
`strings`
|
||||
`html/template`
|
||||
)
|
||||
|
||||
func getIpver(a netip.Addr) (verStr string) {
|
||||
|
||||
if a.Is4() {
|
||||
verStr = "v4"
|
||||
} else if a.Is6() {
|
||||
verStr = "v6"
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getTitle(subPage string) (title string) {
|
||||
|
||||
if subPage == "" || subPage == "index" {
|
||||
@@ -16,3 +29,10 @@ func getTitle(subPage string) (title string) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func safeUrl(urlStr string) (u template.URL) {
|
||||
|
||||
u = template.URL(urlStr)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -4,9 +4,35 @@
|
||||
{{- $linkico := "🔗" }}
|
||||
<h2 id="client">Client/Browser Information<a href="#client">{{ $linkico }}</a></h2>
|
||||
<p>
|
||||
<b>Your 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><a href="https://ipinfo.io/{{ $page.Info.IP.String }}">{{ $page.Info.IP.String }}</a></i>.</b>
|
||||
<br/>
|
||||
<i>You are connecting with port <b>{{ $page.Info.Port }}</b> outbound.</i>
|
||||
{{- if $page.HasAltURL }}
|
||||
<div id="alt_addr"></div>
|
||||
<script>
|
||||
async function fetchAltAddr() {
|
||||
try {
|
||||
const resp = await fetch('{{ safeUrl $page.AltURL }}', {
|
||||
headers: {
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
if (resp.ok) {
|
||||
const dat = await resp.json();
|
||||
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>
|
||||
<br/>
|
||||
<i>Try loading <b><a href="{{ $page.AltURL }}">{{ $page.AltURL }}</a></b> for more information.</i>`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.info('Did not fetch alternate address: ', error);
|
||||
}
|
||||
}
|
||||
fetchAltAddr()
|
||||
</script>
|
||||
{{- end }}
|
||||
</p>
|
||||
{{- if $page.Raw }}
|
||||
<h3 id="client_raw">Raw Block ({{ $page.RawFmt }})<a href="#client_raw">{{ $linkico }}</a></h3>
|
||||
@@ -42,7 +68,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{- /*
|
||||
{{- /*
|
||||
<ul>
|
||||
|
||||
{{- $flds := $ua.ToMap }}
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
`encoding/xml`
|
||||
`net`
|
||||
`net/http`
|
||||
`net/url`
|
||||
`os`
|
||||
"encoding/xml"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
`github.com/mileusna/useragent`
|
||||
`r00t2.io/clientinfo/args`
|
||||
`r00t2.io/goutils/logging`
|
||||
"github.com/mileusna/useragent"
|
||||
"r00t2.io/clientinfo/args"
|
||||
"r00t2.io/goutils/logging"
|
||||
)
|
||||
|
||||
type outerRenderer func(page *Page, resp http.ResponseWriter) (err error)
|
||||
type XmlHeaders map[string][]string
|
||||
type (
|
||||
outerRenderer func(page *Page, resp http.ResponseWriter) (err error)
|
||||
XmlHeaders map[string][]string
|
||||
)
|
||||
|
||||
// R00tInfo is the structure of data returned to the client.
|
||||
type R00tInfo struct {
|
||||
@@ -22,7 +25,7 @@ type R00tInfo struct {
|
||||
// Client is the UA/Client info, if any passed by the client.
|
||||
Client []*R00tClient `json:"ua,omitempty" xml:"ua,omitempty" yaml:"Client/User Agent,omitempty"`
|
||||
// IP is the client IP address.
|
||||
IP net.IP `json:"ip" xml:"ip,attr" yaml:"Client IP Address"`
|
||||
IP netip.Addr `json:"ip" xml:"ip,attr" yaml:"Client IP Address"`
|
||||
// Port is the client's port number.
|
||||
Port uint16 `json:"port" xml:"port,attr" yaml:"Client Port"`
|
||||
// Headers are the collection of the request headers sent by the client.
|
||||
@@ -84,19 +87,23 @@ type Page struct {
|
||||
Indent string
|
||||
// DoIndent indicates if indenting was enabled.
|
||||
DoIndent bool
|
||||
srv *Server
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
log logging.Logger
|
||||
args *args.Args
|
||||
listenUri *url.URL
|
||||
isHttp bool
|
||||
mux *http.ServeMux
|
||||
sock net.Listener
|
||||
doneChan chan bool
|
||||
stopChan chan os.Signal
|
||||
reloadChan chan os.Signal
|
||||
isStopping bool
|
||||
log logging.Logger
|
||||
args *args.Args
|
||||
listenUri *url.URL
|
||||
isHttp bool
|
||||
mux *http.ServeMux
|
||||
sock net.Listener
|
||||
doneChan chan bool
|
||||
stopChan chan os.Signal
|
||||
reloadChan chan os.Signal
|
||||
v4Url *url.URL
|
||||
v6Url *url.URL
|
||||
corsOrigins map[string]struct{}
|
||||
isStopping bool
|
||||
}
|
||||
|
||||
// https://www.iana.org/assignments/media-types/media-types.xhtml
|
||||
|
||||
Reference in New Issue
Block a user