d9e87a3dfe
The card puts the address websites see next to the server's address. Through a full tunnel both are the server's, and the card says "You are protected"; directly or through a split tunnel it says "Not protected", with the visitor's address and its location.
146 lines
3.7 KiB
Go
146 lines
3.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"net/http"
|
|
"net/netip"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// visitorView tells the dashboard whether the browser asking is behind the
|
|
// VPN. Behind it, websites see the server's address, so the card shows the
|
|
// visitor's address next to the server's: the same means protected.
|
|
type visitorView struct {
|
|
Protected bool `json:"protected"`
|
|
IP string `json:"ip"` // what websites see; the server's address when protected
|
|
ServerIP string `json:"serverIP"` // empty when the endpoint does not resolve
|
|
Peer *peerRef `json:"peer"` // the peer whose tunnel the request came through
|
|
Location *GeoInfo `json:"location"` // of IP, when not protected
|
|
}
|
|
|
|
type peerRef struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
// classifyVisitor decides on the address the server sees (remoteIP).
|
|
// Through the tunnel that is the peer's tunnel address; the peer is
|
|
// protected when its AllowedIPs send all traffic of that family through
|
|
// the server. A server behind NAT may instead see its own or its public
|
|
// address, when the request loops back through the router.
|
|
func classifyVisitor(c *Config, ip, server netip.Addr, own func(netip.Addr) bool) visitorView {
|
|
v := visitorView{IP: ip.String()}
|
|
if server.IsValid() {
|
|
v.ServerIP = server.String()
|
|
}
|
|
if p := peerByTunnelAddr(c, ip); p != nil {
|
|
v.Peer = &peerRef{ID: p.ID, Name: p.Name}
|
|
v.Protected = fullTunnel(peerAllowedIPs(c, p), ip.Is6())
|
|
} else if !ip.IsLoopback() && !ip.IsLinkLocalUnicast() && (ip == server || own(ip)) {
|
|
v.Protected = true
|
|
}
|
|
if v.Protected && server.IsValid() {
|
|
v.IP = v.ServerIP
|
|
}
|
|
return v
|
|
}
|
|
|
|
func peerByTunnelAddr(c *Config, ip netip.Addr) *Peer {
|
|
for i := range c.Peers {
|
|
p := &c.Peers[i]
|
|
if !p.Enabled {
|
|
continue
|
|
}
|
|
for _, a := range peerAddresses(c, p) {
|
|
if a.Addr() == ip {
|
|
return p
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// fullTunnel reports whether AllowedIPs route all IPv4 (or IPv6) traffic
|
|
// into the tunnel: 0.0.0.0/0, ::/0, or the two halves some clients use.
|
|
func fullTunnel(allowed []string, v6 bool) bool {
|
|
halves := 0
|
|
for _, s := range allowed {
|
|
p, err := netip.ParsePrefix(s)
|
|
if err != nil || p.Addr().Is6() != v6 {
|
|
continue
|
|
}
|
|
switch p.Bits() {
|
|
case 0:
|
|
return true
|
|
case 1:
|
|
halves++
|
|
}
|
|
}
|
|
return halves >= 2
|
|
}
|
|
|
|
// ownAddr reports whether ip is an address of this machine.
|
|
func ownAddr(ip netip.Addr) bool {
|
|
addrs, err := net.InterfaceAddrs()
|
|
if err != nil {
|
|
return false
|
|
}
|
|
for _, a := range addrs {
|
|
if n, ok := a.(*net.IPNet); ok {
|
|
if x, ok := netip.AddrFromSlice(n.IP); ok && x.Unmap() == ip {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// endpointIPs resolves the WireGuard endpoint, the address websites see
|
|
// behind the VPN, and keeps the answer for a few minutes.
|
|
type endpointIPs struct {
|
|
mu sync.Mutex
|
|
host string
|
|
addrs []netip.Addr
|
|
at time.Time
|
|
}
|
|
|
|
func (e *endpointIPs) lookup(host string, v6 bool) netip.Addr {
|
|
if ip, err := netip.ParseAddr(host); err == nil {
|
|
return ip.Unmap()
|
|
}
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
if host != e.host || time.Since(e.at) > 10*time.Minute {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
addrs, _ := net.DefaultResolver.LookupNetIP(ctx, "ip", host)
|
|
cancel()
|
|
e.host, e.addrs, e.at = host, addrs, time.Now()
|
|
}
|
|
var other netip.Addr
|
|
for _, a := range e.addrs {
|
|
a = a.Unmap()
|
|
if a.Is6() == v6 {
|
|
return a
|
|
}
|
|
if !other.IsValid() {
|
|
other = a
|
|
}
|
|
}
|
|
return other
|
|
}
|
|
|
|
func (a *App) visitor(r *http.Request, c *Config) visitorView {
|
|
ip, err := netip.ParseAddr(remoteIP(r))
|
|
if err != nil {
|
|
return visitorView{IP: remoteIP(r)}
|
|
}
|
|
ip = ip.Unmap()
|
|
v := classifyVisitor(c, ip, a.endpoint.lookup(c.Server.Endpoint, ip.Is6()), ownAddr)
|
|
if !v.Protected && v.Peer == nil {
|
|
v.Location = a.geo.Lookup(v.IP)
|
|
}
|
|
return v
|
|
}
|