perkeep/internal/netutil/netutil.go

184 lines
4.8 KiB
Go

/*
Copyright 2014 The Perkeep Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package netutil
import (
"errors"
"fmt"
"net"
"net/url"
"strings"
"time"
)
// AwaitReachable tries to make a TCP connection to addr regularly.
// It returns an error if it's unable to make a connection before maxWait.
func AwaitReachable(addr string, maxWait time.Duration) error {
done := time.Now().Add(maxWait)
for time.Now().Before(done) {
c, err := net.Dial("tcp", addr)
if err == nil {
c.Close()
return nil
}
time.Sleep(100 * time.Millisecond)
}
return fmt.Errorf("%v unreachable for %v", addr, maxWait)
}
// HostPort takes a urlStr string URL, and returns a host:port string suitable
// to passing to net.Dial, with the port set as the scheme's default port if
// absent.
func HostPort(urlStr string) (string, error) {
// TODO: rename this function to URLHostPort instead, like
// ListenHostPort below.
u, err := url.Parse(urlStr)
if err != nil {
return "", fmt.Errorf("could not parse %q as a url: %v", urlStr, err)
}
if u.Scheme == "" {
return "", fmt.Errorf("url %q has no scheme", urlStr)
}
hostPort := u.Host
if hostPort == "" || strings.HasPrefix(hostPort, ":") {
return "", fmt.Errorf("url %q has no host", urlStr)
}
idx := strings.Index(hostPort, "]")
if idx == -1 {
idx = 0
}
if !strings.Contains(hostPort[idx:], ":") {
if u.Scheme == "https" {
hostPort += ":443"
} else {
hostPort += ":80"
}
}
return hostPort, nil
}
// ListenHostPort maps a listen address into a host:port string.
// If the host part in listenAddr is empty or 0.0.0.0, localhost
// is used instead.
func ListenHostPort(listenAddr string) (string, error) {
hp := listenAddr
if strings.HasPrefix(hp, ":") {
hp = "localhost" + hp
} else if strings.HasPrefix(hp, "0.0.0.0:") {
hp = "localhost:" + hp[len("0.0.0.0:"):]
}
if _, _, err := net.SplitHostPort(hp); err != nil {
return "", err
}
return hp, nil
}
// ListenOnLocalRandomPort returns a TCP listener on a random
// localhost port.
func ListenOnLocalRandomPort() (net.Listener, error) {
ip, err := Localhost()
if err != nil {
return nil, err
}
return net.ListenTCP("tcp", &net.TCPAddr{IP: ip, Port: 0})
}
// Localhost returns the first address found when
// doing a lookup of "localhost". If not successful,
// it looks for an ip on the loopback interfaces.
func Localhost() (net.IP, error) {
if ip := localhostLookup(); ip != nil {
return ip, nil
}
if ip := loopbackIP(); ip != nil {
return ip, nil
}
return nil, errors.New("no loopback ip found")
}
// localhostLookup looks for a loopback IP by resolving localhost.
func localhostLookup() net.IP {
if ips, err := net.LookupIP("localhost"); err == nil && len(ips) > 0 {
return ips[0]
}
return nil
}
// loopbackIP returns the first loopback IP address sniffing network
// interfaces or nil if none is found.
func loopbackIP() net.IP {
interfaces, err := net.Interfaces()
if err != nil {
return nil
}
for _, inf := range interfaces {
const flagUpLoopback = net.FlagUp | net.FlagLoopback
if inf.Flags&flagUpLoopback == flagUpLoopback {
addrs, _ := inf.Addrs()
for _, addr := range addrs {
ip, _, err := net.ParseCIDR(addr.String())
if err == nil && ip.IsLoopback() {
return ip
}
}
}
}
return nil
}
// RandPort returns a random port to listen on.
func RandPort() (int, error) {
var port int
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
if err != nil {
return port, err
}
listener, err := net.ListenTCP("tcp", addr)
if err != nil {
return port, fmt.Errorf("could not listen to find random port: %v", err)
}
randAddr := listener.Addr().(*net.TCPAddr)
if err := listener.Close(); err != nil {
return port, fmt.Errorf("could not close random listener: %v", err)
}
return randAddr.Port, nil
}
// HasPort when given a string of the form "host", "host:port", or
// "[ipv6::address]:port", returns true if the string includes a port.
func HasPort(s string) bool {
return strings.LastIndex(s, ":") > strings.LastIndex(s, "]")
}
// IsFQDN reports whether domain looks like a fully qualified domain name.
func IsFQDN(domain string) bool {
// TODO(mpl): there's probably a regexp for all this...
if domain == "localhost" {
return false
}
if !strings.Contains(domain, ".") {
return false
}
if strings.Contains(domain, "/") {
return false
}
if net.ParseIP(domain) != nil {
return false
}
return true
}