primeiro commit
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
// Package network discovers the IPv4/IPv6 addresses to publish to dynamic DNS.
|
||||
package network
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const httpTimeout = 10 * time.Second
|
||||
|
||||
// PublicIPv4Services and PublicIPv6Services are queried in order; the first
|
||||
// service that returns a usable address wins.
|
||||
var (
|
||||
PublicIPv4Services = []string{
|
||||
"https://api.ipify.org",
|
||||
"https://ipv4.icanhazip.com",
|
||||
}
|
||||
PublicIPv6Services = []string{
|
||||
"https://api6.ipify.org",
|
||||
"https://ipv6.icanhazip.com",
|
||||
}
|
||||
)
|
||||
|
||||
// Addresses holds the discovered addresses for a single update run.
|
||||
type Addresses struct {
|
||||
IPv4 net.IP
|
||||
IPv6 net.IP
|
||||
}
|
||||
|
||||
// Empty reports whether no address was discovered at all.
|
||||
func (a Addresses) Empty() bool {
|
||||
return a.IPv4 == nil && a.IPv6 == nil
|
||||
}
|
||||
|
||||
// Discover finds the addresses to publish. If iface is non-empty its
|
||||
// addresses are used; otherwise the public IP is guessed via external
|
||||
// services, independently for IPv4 and IPv6.
|
||||
func Discover(iface string) (Addresses, error) {
|
||||
if iface != "" {
|
||||
return fromInterface(iface)
|
||||
}
|
||||
return fromPublicServices(), nil
|
||||
}
|
||||
|
||||
func fromInterface(name string) (Addresses, error) {
|
||||
ifi, err := net.InterfaceByName(name)
|
||||
if err != nil {
|
||||
return Addresses{}, fmt.Errorf("looking up interface %s: %w", name, err)
|
||||
}
|
||||
addrs, err := ifi.Addrs()
|
||||
if err != nil {
|
||||
return Addresses{}, fmt.Errorf("listing addresses on %s: %w", name, err)
|
||||
}
|
||||
|
||||
var out Addresses
|
||||
for _, a := range addrs {
|
||||
ipNet, ok := a.(*net.IPNet)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
ip := ipNet.IP
|
||||
if !isUsable(ip) {
|
||||
continue
|
||||
}
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
if out.IPv4 == nil {
|
||||
out.IPv4 = ip4
|
||||
}
|
||||
} else if out.IPv6 == nil {
|
||||
out.IPv6 = ip
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func isUsable(ip net.IP) bool {
|
||||
return !ip.IsLoopback() && !ip.IsLinkLocalUnicast() && !ip.IsLinkLocalMulticast()
|
||||
}
|
||||
|
||||
func fromPublicServices() Addresses {
|
||||
return Addresses{
|
||||
IPv4: queryFirst(PublicIPv4Services, false),
|
||||
IPv6: queryFirst(PublicIPv6Services, true),
|
||||
}
|
||||
}
|
||||
|
||||
func queryFirst(services []string, wantIPv6 bool) net.IP {
|
||||
for _, url := range services {
|
||||
ip, err := queryOne(url)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
isV6 := ip.To4() == nil
|
||||
if isV6 != wantIPv6 {
|
||||
continue
|
||||
}
|
||||
return ip
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func queryOne(url string) (net.IP, error) {
|
||||
client := http.Client{Timeout: httpTimeout}
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%s: unexpected status %s", url, resp.Status)
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(io.LimitReader(resp.Body, 256))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ip := net.ParseIP(strings.TrimSpace(string(body)))
|
||||
if ip == nil {
|
||||
return nil, fmt.Errorf("%s: could not parse IP from response", url)
|
||||
}
|
||||
return ip, nil
|
||||
}
|
||||
Reference in New Issue
Block a user