New template (#121)

This commit is contained in:
Thatcher 2020-12-14 19:02:35 +01:00 committed by GitHub
parent 762f454865
commit ab8f90431b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 640 additions and 204 deletions

2
.dockerignore Normal file
View File

@ -0,0 +1,2 @@
Dockerfile
Dockerfile.geoip

View File

@ -9,9 +9,9 @@ RUN make
# Run
FROM scratch
EXPOSE 8080
COPY --from=build \
/go/bin/echoip \
/go/src/github.com/mpolden/echoip/index.html \
/opt/echoip/
COPY --from=build /go/bin/echoip /opt/echoip/
COPY html /opt/echoip/html
WORKDIR /opt/echoip
ENTRYPOINT ["/opt/echoip/echoip"]

View File

@ -69,3 +69,6 @@ ifndef DEST_PATH
endif
rsync -a $(GOPATH)/bin/$(XBIN) $(DEST_PATH)/$(XBIN)
@sha256sum $(GOPATH)/bin/$(XBIN)
run:
go run cmd/echoip/main.go -a data/asn.mmdb -c data/city.mmdb -f data/country.mmdb -H x-forwarded-for -r -s

View File

@ -127,5 +127,5 @@ Usage of echoip:
-p Enable port lookup
-r Perform reverse hostname lookups
-t string
Path to template (default "index.html")
Path to template directory (default "html")
```

View File

@ -41,9 +41,10 @@ func main() {
listen := flag.String("l", ":8080", "Listening address")
reverseLookup := flag.Bool("r", false, "Perform reverse hostname lookups")
portLookup := flag.Bool("p", false, "Enable port lookup")
template := flag.String("t", "index.html", "Path to template")
template := flag.String("t", "html", "Path to template dir")
cacheSize := flag.Int("C", 0, "Size of response cache. Set to 0 to disable")
profile := flag.Bool("P", false, "Enables profiling handlers")
sponsor := flag.Bool("s", false, "Show sponsor logo")
var headers multiValueFlag
flag.Var(&headers, "H", "Header to trust for remote IP, if present (e.g. X-Real-IP)")
flag.Parse()
@ -72,6 +73,10 @@ func main() {
log.Println("Enabling port lookup")
server.LookupPort = iputil.LookupPort
}
if *sponsor {
log.Println("Enabling sponsor logo")
server.Sponsor = *sponsor
}
if len(headers) > 0 {
log.Printf("Trusting remote IP from header(s): %s", headers.String())
}

332
html/index.html Normal file
View File

@ -0,0 +1,332 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>What is my IP address? &mdash; {{ .Host }}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="description"
content="{{ .Host }} • What is my IP address? &mdash; The best tool to find your own IP address, and information about it."
/>
<link rel="canonical" href="https://ifconfig.co/" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,400;0,700;1,400&display=swap"
rel="stylesheet"
/>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/pure/1.0.0/pure-min.css"
integrity="sha384-nn4HPE8lTHyVtfCBi5yW9d20FjT8BJwUXyWZT9InLYax14RDjBj46LmSztkmNP9w"
crossorigin="anonymous"
/>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/pure/1.0.0/grids-responsive-min.css"
integrity="sha384-b92sF+wDNTHrfEtRaYo+EpcA8FUyHOSXrdxKc9XB9kaaX1rSQAgMevW6cYeE5Bdv"
crossorigin="anonymous"
/>
{{ template "script.html" . }} {{ template "styles.html" . }}
</head>
<body>
<div class="content">
<div class="pure-g gutters center">
<div class="pure-u-1 pure-u-md-2-3">
<div class="l-box">
<h1>{{ .Host }} — What is my IP address?</h1>
<p><code class="ip">{{ .IP }}</code></p>
<p>
The best tool to find your own IP address, and information about
it.
</p>
</div>
</div>
<div class="pure-u-1 pure-u-md-1-3">
{{ if .Sponsor }}
<div class="l-box leafcloud-placement">
<div class="pure-g">
<div class="pure-u pure-u-md-1">
<div class="leafcloud-logo">
<a href="https://www.leaf.cloud?utm_source=ifconfig">
{{ template "leafcloud-logo.svg" "." }}
</a>
</div>
</div>
<div class="pure-u pure-u-md-1">
<p>
This site is graciously hosted by<br />
<a href="https://www.leaf.cloud?utm_source=ifconfig">
Leafcloud The Truly Sustainable Cloud
</a>
</p>
</div>
</div>
</div>
{{ end }}
</div>
</div>
<div class="pure-g gutters center">
<!-- COLUMN 1 -->
<div class="pure-u-1 pure-u-md-1-2 col">
<div class="l-box">
<h2>What do we know about this IP Address?</h2>
<table class="info-table">
<tr>
<th scope="row">IP Address</th>
<td>{{ .IP }}</td>
</tr>
<tr>
<th scope="row">IP Decimal</th>
<td>{{ .IPDecimal }}</td>
</tr>
{{ if .Country }}
<tr>
<th scope="row">Country</th>
<td>{{ .Country }}</td>
</tr>
{{ end }} {{ if .CountryISO }}
<tr>
<th scope="row">CountryISO</th>
<td>{{ .CountryISO }}</td>
</tr>
{{ end }} {{ if .CountryEU }}
<tr>
<th scope="row">CountryEU</th>
<td>{{ .CountryEU }}</td>
</tr>
{{ end }} {{ if .RegionName }}
<tr>
<th scope="row">RegionName</th>
<td>{{ .RegionName }}</td>
</tr>
{{ end }} {{ if .RegionCode }}
<tr>
<th scope="row">RegionCode</th>
<td>{{ .RegionCode }}</td>
</tr>
{{ end }} {{ if .MetroCode }}
<tr>
<th scope="row">MetroCode</th>
<td>{{ .MetroCode }}</td>
</tr>
{{ end }} {{ if .PostalCode }}
<tr>
<th scope="row">PostalCode</th>
<td>{{ .PostalCode }}</td>
</tr>
{{ end }} {{ if .City }}
<tr>
<th scope="row">City</th>
<td>{{ .City }}</td>
</tr>
{{ end }} {{ if .Latitude }}
<tr>
<th scope="row">Latitude</th>
<td>{{ .Latitude }}</td>
</tr>
{{ end }} {{ if .Longitude }}
<tr>
<th scope="row">Longitude</th>
<td>{{ .Longitude }}</td>
</tr>
{{ end }} {{ if .Timezone }}
<tr>
<th scope="row">Timezone</th>
<td>{{ .Timezone }}</td>
</tr>
{{ end }} {{ if .ASN }}
<tr>
<th scope="row">ASN</th>
<td>{{ .ASN }}</td>
</tr>
{{ end }} {{ if .ASNOrg }}
<tr>
<th scope="row">ASNOrg</th>
<td>{{ .ASNOrg }}</td>
</tr>
{{ end }} {{ if .Hostname }}
<tr>
<th scope="row">Hostname</th>
<td>{{ .Hostname }}</td>
</tr>
{{ end }} {{ if .UserAgent }} {{ if .UserAgent.Comment }}
<tr>
<th scope="row">User Agent</th>
<td>{{ .UserAgent.Product }}/{{ .UserAgent.Version }}</td>
</tr>
{{ end }} {{ if .UserAgent.Comment }}
<tr>
<th scope="row">Comment</th>
<td>{{ .UserAgent.Comment }}</td>
</tr>
{{ end }} {{ if .UserAgent.RawValue }}
<tr>
<th scope="row">Raw Value</th>
<td>{{ .UserAgent.RawValue }}</td>
</tr>
{{ end }} {{ end }}
</table>
{{ if .Country }}
<p>
This information is provided from the GeoLite2 database created by
MaxMind, available from
<a href="https://www.maxmind.com">www.maxmind.com</a>
</p>
{{ end }} {{ if .Latitude }}
<div class="pure-u-1 pure-u-md-1-1">
<h2>Map</h2>
<iframe
width="100%"
height="350"
frameborder="0"
scrolling="no"
marginheight="0"
marginwidth="0"
src="https://www.openstreetmap.org/export/embed.html?bbox={{ .BoxLonLeft }}%2C{{ .BoxLatBottom }}%2C{{ .BoxLonRight }}%2C{{ .BoxLatTop }}&amp;layer=mapnik&amp;marker={{ .Latitude }}%2C{{ .Longitude }}"
></iframe>
</div>
{{ end }}
</div>
</div>
<!-- COLUMN 2 -->
<div class="pure-u-1 pure-u-md-1-2">
<div class="l-box">
<h2>How do I get this programmatically?</h2>
<p>
With the widget below you can build your query, and see what the
result will look like.
</p>
<div class="pure-form">
<!-- COMMAND WIDGET -->
<div class="buttons">
<button
name="ip"
class="button widget-select"
onclick="changeInput(this.name, this)"
>
ip
</button>
<button
name="country"
class="button widget-select"
onclick="changeInput(this.name, this)"
>
country
</button>
<button
name="country-iso"
class="button widget-select"
onclick="changeInput(this.name, this)"
>
country-iso
</button>
<button
name="city"
class="button widget-select"
onclick="changeInput(this.name, this)"
>
city
</button>
<button
name="asn"
class="button widget-select"
onclick="changeInput(this.name, this)"
>
asn
</button>
<button
name="json"
class="button widget-select"
onclick="changeInput(this.name, this)"
>
json
</button>
<button
name="port"
class="button widget-select"
onclick="changeInput(this.name, this)"
>
port
</button>
<input
id="portInput"
type="number"
min="1"
max="40000"
value="8080"
class="narrow-input pure-input"
placeholder="8080"
onchange="updatePort(this.value)"
/>
</div>
<div class="widgetbox input">
<code id="command"></code>
</div>
<div id="output" class="widgetbox output"></div>
<form class="pure-form">
<fieldset>
<label for="ipCheckBox">
Check another IP
<input
id="ipInput"
class="medium-input pure-input"
type="text"
placeholder="1.1.1.1"
onkeyup="updateIP(this.value)"
/>
</label>
<button
type="button"
class="pure-button"
onclick="navigate()"
>
Open
</button>
</fieldset>
</form>
</div>
<!-- FAQ -->
<div class="FAQ">
<h2>FAQ</h2>
<h3>How do I force IPv4 or IPv6 lookup?</h3>
<p>
As of 2018-07-25 it's no longer possible to force protocol using
the
<i>v4</i> and <i>v6</i> subdomains. IPv4 or IPv6 still can be
forced by passing the appropiate flag to your client, e.g
<code>curl -4</code> or <code>curl -6</code>.
</p>
<h3>Can I force getting JSON?</h3>
<p>
Setting the <code>Accept: application/json</code> header works
as expected.
</p>
<h3>Is automated use of this service permitted?</h3>
<p>
Yes, as long as the rate limit is respected. The rate limit is
in place to ensure a fair service for all.
</p>
<p>
<em>Please limit automated requests to 1 request per minute</em
>. No guarantee is made for requests that exceed this limit.
They may be rate-limited, with a 429 status code, or dropped
entirely.
</p>
<h3>Can I run my own service?</h3>
<p>
Yes, the source code and documentation is available on
<a href="https://github.com/mpolden/echoip">GitHub</a>.
</p>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

13
html/leafcloud-logo.svg Normal file
View File

@ -0,0 +1,13 @@
<svg width="96" height="68" viewBox="0 0 96 68" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 20.0787H19.7445C27.6337 20.0787 34.0296 26.4439 34.0296 34.2951V46.1525H14.2851C6.39594 46.1541 0 39.789 0 31.9361V20.0787Z" fill="#32F295" />
<path d="M95.9969 0H61.0472C47.0825 0 35.7608 11.2672 35.7608 25.1647V46.1539H70.7089C84.6736 46.1539 95.9953 34.8867 95.9953 20.9892V0H95.9969Z" fill="#3FA9F5" />
<path d="M1 49.9344H2.67242V67.7596H1V49.9344Z" fill="white" class="letters" />
<path d="M4.36731 61.819C4.36731 58.3727 6.92285 55.6395 10.5524 55.6395C13.6578 55.6395 16.1406 57.8496 16.1648 61.6532V62.1055H6.08664C6.1109 64.5537 7.9014 66.4547 10.5055 66.4547C12.4642 66.4547 13.6336 65.6226 14.4455 64.2205L15.8074 65.0994C14.7561 66.8813 12.9882 67.9984 10.5055 67.9984C6.75464 67.9984 4.36731 65.2411 4.36731 61.819ZM6.23059 60.631H14.4229C14.1366 58.4918 12.4884 57.1848 10.4828 57.1848C8.49823 57.1848 6.63656 58.4677 6.23059 60.631Z" fill="white" class="letters" />
<path d="M17.7646 61.8189C17.7646 58.4194 20.3201 55.6395 23.7119 55.6395C25.5509 55.6395 27.2702 56.5908 28.1307 57.8737V55.8778H29.8031V67.7618H28.1307V65.7658C27.2702 67.0487 25.5509 68 23.7119 68C20.3201 67.9984 17.7646 65.2169 17.7646 61.8189ZM28.273 61.8189C28.273 59.2516 26.4825 57.1848 23.8785 57.1848C21.2744 57.1848 19.4839 59.2532 19.4839 61.8189C19.4839 64.3847 21.2744 66.4531 23.8785 66.4531C26.4825 66.4531 28.273 64.3863 28.273 61.8189Z" fill="white" class="letters" />
<path d="M34.3386 57.4456H31.7831V55.8762H34.3386V53.2138C34.3386 50.8847 35.7717 49.7917 37.7061 49.7917C38.3741 49.7917 38.9952 49.9109 39.4966 50.1008V51.5978C39.1149 51.4545 38.4453 51.337 37.9681 51.337C36.7017 51.337 36.0094 51.836 36.0094 53.3571V55.8762H39.4966V57.4456H36.0094V67.7602H34.337V57.4456H34.3386Z" fill="white" class="letters" />
<path d="M39.6161 61.819C39.6161 58.4194 42.2185 55.6395 45.8497 55.6395C48.3098 55.6395 50.2685 56.8999 51.3182 58.7526L49.8609 59.5848C49.1444 58.1586 47.7113 57.1848 45.848 57.1848C43.1259 57.1848 41.3338 59.2757 41.3338 61.819C41.3338 64.3622 43.1243 66.4531 45.848 66.4531C47.7113 66.4531 49.1444 65.4793 49.8609 64.0531L51.3182 64.8853C50.2669 66.7396 48.3098 67.9984 45.8497 67.9984C42.2202 67.9984 39.6161 65.2169 39.6161 61.819Z" fill="white" class="letters" />
<path d="M53.2057 49.9344H54.8782V67.7596H53.2057V49.9344Z" fill="white" class="letters" />
<path d="M57.0261 61.819C57.0261 58.4194 59.6286 55.6395 63.2597 55.6395C66.8908 55.6395 69.4933 58.421 69.4933 61.819C69.4933 65.2169 66.8892 67.9984 63.2597 67.9984C59.6302 67.9984 57.0261 65.2169 57.0261 61.819ZM67.7723 61.819C67.7723 59.2757 65.9818 57.1848 63.2581 57.1848C60.5343 57.1848 58.7438 59.2757 58.7438 61.819C58.7438 64.3622 60.5343 66.4531 63.2581 66.4531C65.9818 66.4531 67.7723 64.3622 67.7723 61.819Z" fill="white" class="letters" />
<path d="M71.4506 63.3162V55.8765H73.123V63.078C73.123 65.1223 74.3167 66.4534 76.036 66.4534C78.0417 66.4534 79.6187 64.6716 79.6187 61.7951V55.8765H81.2911V67.7605H79.6187V65.7645C78.7824 67.2856 77.3267 67.9987 75.8209 67.9987C73.2896 67.9987 71.4506 66.1685 71.4506 63.3162Z" fill="white" class="letters" />
<path d="M82.9614 61.8184C82.9614 58.4189 85.517 55.639 88.9087 55.639C90.7477 55.639 92.4671 56.5903 93.3275 57.8732V49.9344H95V67.7596H93.3275V65.7637C92.4655 67.0466 90.7461 67.9979 88.9071 67.9979C85.517 67.9979 82.9614 65.2164 82.9614 61.8184ZM93.4699 61.8184C93.4699 59.2511 91.6794 57.1843 89.0753 57.1843C86.4712 57.1843 84.6808 59.2527 84.6808 61.8184C84.6808 64.3842 86.4712 66.4526 89.0753 66.4526C91.6794 66.4526 93.4699 64.3858 93.4699 61.8184Z" fill="white" class="letters" />
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

89
html/script.html Normal file
View File

@ -0,0 +1,89 @@
<script lang="text/javascript">
let host = "{{ .Host }}";
let jsonObj = "{{ .JSON }}";
let data = JSON.parse(jsonObj);
let tool = "curl";
let commandBox, widgetBox, compositePath, commandStr;
let path
let ipQuery, portQuery
let ipCheckBox, portCheckBox, portInput
let ip = ''
window.onload = (event) => {
commandBox = document.getElementById('command');
widgetBox = document.getElementById('output');
ipCheckBox = document.getElementById('ipCheckBox')
portCheckBox = document.getElementById('portCheckBox')
portInput = document.getElementById('portInput')
reset()
setcommdStr()
changeInput("ip")
}
function reset() {
path = '';
ipQuery = '';
portQuery = '';
}
function setcommdStr() {
compositePath = `${path}${portQuery}${ipQuery}`;
commandStr = `${tool} ${host}/${compositePath}`;
commandBox.innerText = commandStr;
}
function changeInput(input, button) {
path = input
portQuery = ""
portInput.classList.add("hidden");
switch(path) {
case "json":
output.innerText = jsonObj
break
case "country-iso":
output.innerText = data["country_iso"]
break
case "port":
portInput.classList.remove("hidden");
path = "port";
output.innerText = "{}";
let currentPort = document.querySelector("#portInput").value;
updatePort(currentPort);
break
case "ip":
output.innerText = data["ip"]
path = ""
break
default:
output.innerText = data[path]
}
setcommdStr();
// set button selected
if (button) {
allButtons = document.querySelectorAll(('button.selected'));
allButtons.forEach((btn) => {btn.classList.remove("selected")})
button.classList.add("selected");
}
}
function navigate(event) {
console.log("navigate", compositePath)
window.location = compositePath
event.preventDefault()
}
function updatePort(value) {
port = value
portQuery = `/${port}`
setcommdStr()
}
function updateIP(value) {
ip = value
ipQuery = `?ip=${ip}`;
setcommdStr()
changeInput("ip", null)
}
</script>

185
html/styles.html Normal file
View File

@ -0,0 +1,185 @@
<style>
html,
.pure-g [class*="pure-u"] {
background-color: white;
font-family: "Open Sans", sans-serif;
}
pre {
font-family: "Monaco", "Menlo", "Consolas", "Courier New", monospace;
white-space: pre-wrap; /* Since CSS 2.1 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word;
}
a {
/* background: #e3e3e3; */
text-decoration: underline;
color: #000;
}
a:hover,
active {
background: #d7d7d7;
}
.ip {
border: 1px solid #cbcbcb;
background: #f2f2f2;
font-size: 36px;
padding: 6px;
}
svg.github-corner {
fill: #151513;
color: #fff;
}
.footer {
margin-top: 34px;
border-top: 1px solid #cbcbcb;
}
.content {
margin-left: auto;
margin-right: auto;
padding-left: 1em;
padding-right: 1em;
max-width: 1024px;
}
.center {
justify-content: center;
}
.info-table td,
.info-table th {
padding: 5px;
border: 2px solid #ababab;
}
.info-table th[scope="row"] {
background-color: #d5d5d5;
text-align: left;
white-space: nowrap;
}
.widgetbox {
width: 100%;
padding: 0.5rem;
border: 1px solid grey;
font-family: "Courier New", Courier, monospace;
margin-top: 0.5rem;
box-sizing: border-box;
}
.widgetbox.input :first-child::before {
content: "$ ";
white-space: pre;
}
.widgetbox.output {
min-height: 4em;
white-space: pre;
overflow-x: scroll;
}
.l-box {
margin: 0 1rem;
}
.align-right {
text-align: right;
}
.narrow-input {
width: 5.5em;
height: 1.3em;
margin-top: 0.2em;
}
.medium-input {
width: 10em;
}
button.selected {
background-color: rgb(208 208 208);
}
/* POST CORRECTION */
.leafcloud-logo .letters {
fill: black;
}
/* DARK MODE OVERRIDES */
@media (prefers-color-scheme: dark) {
html,
.pure-g [class*="pure-u"],
a {
background-color: #161719;
color: #d8d9da;
/* text-decoration: underline; */
}
.ip {
border: 1px solid #313233;
background: #212223;
}
.footer {
color: #8e8e8e !important;
border-top: 1px solid #313233;
}
a:hover,
active {
background: #3d3e3f;
}
svg.github-corner {
fill: #f8f9fa;
color: #161719;
}
.info-table th[scope="row"] {
background-color: #2e2e2e;
color: rgb(220, 220, 220);
text-align: left;
white-space: nowrap;
}
button {
background-color: #2e2e2e;
}
button.selected {
background-color: rgb(125 125 125);
}
.pure-input {
background-color: #e6e6e6;
color: #666;
}
.pure-input::placeholder {
color: #bbb;
}
.leafcloud-logo .letters {
fill: white;
max-width: 100%;
}
}
@media (min-width: 768px) {
.leafcloud-placement {
text-align: right;
}
.leafcloud-logo {
height: 80px;
margin: 2em 0 -0.5em 0;
}
}
@media (max-width: 768px) {
.leafcloud-logo {
margin-right: 1em;
}
}
.debug {
outline: 1px dotted pink;
}
</style>

View File

@ -34,6 +34,7 @@ type Server struct {
cache *Cache
gr geo.Reader
profile bool
Sponsor bool
}
type Response struct {
@ -322,7 +323,7 @@ func (s *Server) DefaultHandler(w http.ResponseWriter, r *http.Request) *appErro
if err != nil {
return badRequest(err).WithMessage(err.Error())
}
t, err := template.ParseFiles(s.Template)
t, err := template.ParseGlob(s.Template + "/*")
if err != nil {
return internalServerError(err)
}
@ -330,6 +331,7 @@ func (s *Server) DefaultHandler(w http.ResponseWriter, r *http.Request) *appErro
if err != nil {
return internalServerError(err)
}
var data = struct {
Response
Host string
@ -339,6 +341,7 @@ func (s *Server) DefaultHandler(w http.ResponseWriter, r *http.Request) *appErro
BoxLonRight float64
JSON string
Port bool
Sponsor bool
}{
response,
r.Host,
@ -348,6 +351,7 @@ func (s *Server) DefaultHandler(w http.ResponseWriter, r *http.Request) *appErro
response.Longitude + 0.05,
string(json),
s.LookupPort != nil,
s.Sponsor,
}
if err := t.Execute(w, &data); err != nil {
return internalServerError(err)

View File

@ -1,197 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>What is my IP address? &mdash; {{ .Host }}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="What is my IP address?">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans" integrity="sha384-IL0QWVE358zSFpPhnzSOq+Fb0tBsBCW9cgOfJcUy6xOGUtEY02XEbz+kPcc2ehnP" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/pure/1.0.0/pure-min.css" integrity="sha384-nn4HPE8lTHyVtfCBi5yW9d20FjT8BJwUXyWZT9InLYax14RDjBj46LmSztkmNP9w" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/pure/1.0.0/grids-responsive-min.css" integrity="sha384-b92sF+wDNTHrfEtRaYo+EpcA8FUyHOSXrdxKc9XB9kaaX1rSQAgMevW6cYeE5Bdv" crossorigin="anonymous">
<style>
html, .pure-g [class *= "pure-u"] {
background-color: white;
font-family: "Open Sans", sans-serif;
}
pre {
font-family: "Monaco", "Menlo", "Consolas", "Courier New", monospace;
white-space: pre-wrap; /* Since CSS 2.1 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word;
}
body {
margin-left: auto;
margin-right: auto;
max-width: 80%;
margin-bottom: 10px;
}
a {
background: #e3e3e3;
text-decoration: none;
color: #000;
}
a:hover, active {
background: #d7d7d7;
}
.ip {
border: 1px solid #cbcbcb;
background: #f2f2f2;
font-size: 36px;
padding: 6px;
}
svg.github-corner {
fill: #151513;
color: #fff;
}
.footer {
margin-top: 34px;
border-top: 1px solid #cbcbcb;
}
@media (prefers-color-scheme: dark) {
html, .pure-g [class *= "pure-u"], a {
background-color: #161719;
color:#d8d9da;
text-decoration: none;
}
.ip {
border: 1px solid #313233;
background: #212223;
}
.footer {
color: #8e8e8e !important;
border-top: 1px solid #313233;
}
a {
background: #313233;
}
a:hover, active {
background: #3d3e3f;
}
svg.github-corner {
fill: #f8f9fa;
color: #161719;
}
}
</style>
</head>
<body>
<div class="pure-g">
<div class="pure-u-1-1">
<h1>What is my IP address?</h1>
<p><code class="ip">{{ .IP }}</code></p>
<p>Multiple command line HTTP clients are supported,
including <a href="https://curl.haxx.se/">curl</a>, <a href="https://github.com/jkbrzt/httpie">httpie</a>, <a href="https://github.com/nojima/httpie-go">httpie-go</a>, <a href="https://www.gnu.org/software/wget/">GNU
Wget</a>, <a href="https://www.freebsd.org/cgi/man.cgi?fetch(1)">fetch</a>, and <a href="https://github.com/astaxie/bat">bat</a>.</p>
<p>All endpoints, with the exception of <code>/port</code>, can return information about a custom IP address specified via <code>?ip=</code> query parameter.</p>
</div>
</div>
<div class="pure-g">
<div class="pure-u-1 pure-u-md-1-2">
<h2>CLI examples</h2>
<pre>
$ curl {{ .Host }}
{{ .IP }}
$ http -b {{ .Host }}
{{ .IP }}
$ ht -b {{ .Host }}
{{ .IP }}
$ wget -qO- {{ .Host }}
{{ .IP }}
$ fetch -qo- http://{{ .Host }}
{{ .IP }}
$ bat -print=b {{ .Host }}/ip
{{ .IP }}</pre>
{{ if .Country }}
<h2>Country lookup</h2>
<pre>
$ http {{ .Host }}/country
{{ .Country }}
$ http {{ .Host }}/country-iso
{{ .CountryISO }}</pre>
{{ end }}
{{ if .City }}
<h2>City lookup</h2>
<pre>
$ http {{ .Host }}/city
{{ .City }}</pre>
{{ end }}
{{ if .ASN }}
<h2>ASN lookup</h2>
<pre>
$ http {{ .Host }}/asn
{{ .ASN }}
{{ if .ASNOrg }}</pre>
{{ end }}
{{ end }}
</div>
<div class="pure-u-1 pure-u-md-1-2">
<h2>JSON output</h2>
<pre>
$ http {{ .Host }}/json
{{ .JSON }}</pre>
<p>Setting the <code>Accept: application/json</code> header also works as expected.</p>
<h2>Plain output</h2>
<p>Always returns the IP address including a trailing newline, regardless of user agent.</p>
<pre>
$ http {{ .Host }}/ip
{{ .IP }}</pre>
{{ if .Port }}
<h2>Port testing</h2>
<pre>
$ http {{ .Host }}/port/8080
{
"ip": "{{ .IP }}",
"port": 8080,
"reachable": false
}</pre>
{{ end }}
</div>
{{ if .City }}
<div class="pure-u-1 pure-u-md-1-1">
<h2>Map</h2>
<iframe width="100%" height="350" frameborder="0" scrolling="no" marginheight="0" marginwidth="0" src="https://www.openstreetmap.org/export/embed.html?bbox={{ .BoxLonLeft }}%2C{{ .BoxLatBottom }}%2C{{ .BoxLonRight }}%2C{{ .BoxLatTop }}&amp;layer=mapnik&amp;marker={{ .Latitude }}%2C{{ .Longitude }}"></iframe>
</div>
{{ end }}
<div class="pure-u-1 pure-u-md-1-2">
<h2>FAQ</h2>
<h3>How do I force IPv4 or IPv6 lookup?</h3>
<p>As of 2018-07-25 it's no longer possible to force protocol using
the <i>v4</i> and <i>v6</i> subdomains. IPv4 or IPv6 still can be forced
by passing the appropiate flag to your client, e.g <code>curl -4</code>
or <code>curl -6</code>.</p>
<h3>Is automated use of this service permitted?</h3>
<p>
Yes, as long as the rate limit is respected. The rate limit is in
place to ensure a fair service for all.
</p>
<p>
<em>Please limit automated requests to 1 request per minute</em>. No
guarantee is made for requests that exceed this limit. They may be
rate-limited, with a 429 status code, or dropped entirely.
</p>
<h3>Can I run my own service?</h3>
<p>Yes, the source code and documentation is available on <a href="https://github.com/mpolden/echoip">GitHub</a>.</p>
</div>
</div>
<a href="https://github.com/mpolden/echoip" class="github-corner"><svg class="github-corner" width="80" height="80" viewBox="0 0 250 250" style="position: fixed; top: 0; border: 0; right: 0;"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a><style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style>
{{ if or .Country .City .ASN .ASNOrg }}
<div class="pure-g">
<div class="pure-u-1-1 footer">
<p><small>This product includes GeoLite2 data created by MaxMind,
available from
<a href="https://www.maxmind.com">https://www.maxmind.com</a>.</small></p>
</div>
</div>
{{ end }}
</body>
</html>