2015-02-24 08:55:24 +00:00
|
|
|
package hostess
|
|
|
|
|
2015-02-24 12:17:31 +00:00
|
|
|
import (
|
2015-02-25 10:33:18 +00:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
2015-02-24 12:17:31 +00:00
|
|
|
"net"
|
2015-03-02 07:15:02 +00:00
|
|
|
"sort"
|
2015-02-24 12:17:31 +00:00
|
|
|
)
|
|
|
|
|
2015-03-02 08:22:05 +00:00
|
|
|
// ErrInvalidVersionArg is raised when a function expects IPv 4 or 6 but is
|
|
|
|
// passed a value not 4 or 6.
|
|
|
|
var ErrInvalidVersionArg = errors.New("Version argument must be 4 or 6")
|
2015-03-02 05:54:51 +00:00
|
|
|
|
2015-03-22 00:07:58 +00:00
|
|
|
// Hostlist is an ordered set of Hostnames. When in a Hostlist, Hostnames must
|
2015-03-02 08:22:05 +00:00
|
|
|
// follow some rules:
|
2015-03-02 10:20:34 +00:00
|
|
|
//
|
2015-03-22 00:07:58 +00:00
|
|
|
// - Hostlist may contain IPv4 AND IPv6 ("IP version" or "IPv") Hostnames.
|
2015-03-02 10:20:34 +00:00
|
|
|
// - Names are only allowed to overlap if IP version is different.
|
|
|
|
// - Adding a Hostname for an existing name will replace the old one.
|
|
|
|
//
|
2015-03-02 08:22:05 +00:00
|
|
|
// See docs for the Sort and Add for more details.
|
2015-02-25 10:33:18 +00:00
|
|
|
type Hostlist []*Hostname
|
|
|
|
|
2015-03-02 08:22:05 +00:00
|
|
|
// NewHostlist initializes a new Hostlist
|
2015-02-25 10:33:18 +00:00
|
|
|
func NewHostlist() *Hostlist {
|
|
|
|
return &Hostlist{}
|
|
|
|
}
|
|
|
|
|
2015-03-02 08:55:34 +00:00
|
|
|
// Len returns the number of Hostnames in the list, part of sort.Interface
|
2015-03-02 07:15:02 +00:00
|
|
|
func (h Hostlist) Len() int {
|
|
|
|
return len(h)
|
|
|
|
}
|
|
|
|
|
2015-03-22 01:11:09 +00:00
|
|
|
// MakeSurrogateIP takes an IP like 127.0.0.1 and munges it to 0.0.0.1 so we can
|
|
|
|
// sort it more easily.
|
|
|
|
func MakeSurrogateIP(IP net.IP) net.IP {
|
|
|
|
if string(IP[0:3]) == "127" {
|
|
|
|
return net.IP("0" + string(IP[3:]))
|
|
|
|
}
|
|
|
|
return IP
|
|
|
|
}
|
|
|
|
|
2015-03-02 08:55:34 +00:00
|
|
|
// Less determines the sort order of two Hostnames, part of sort.Interface
|
2015-03-22 00:43:27 +00:00
|
|
|
func (h Hostlist) Less(A, B int) bool {
|
2015-03-22 01:11:09 +00:00
|
|
|
// Sort "localhost" at the top
|
2015-03-22 00:43:27 +00:00
|
|
|
if h[A].Domain == "localhost" {
|
2015-03-02 07:15:02 +00:00
|
|
|
return true
|
|
|
|
}
|
2015-03-22 00:43:27 +00:00
|
|
|
if h[B].Domain == "localhost" {
|
2015-03-02 07:15:02 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2015-03-02 08:22:05 +00:00
|
|
|
// Sort IPv4 before IPv6
|
2015-03-22 00:43:27 +00:00
|
|
|
// A is IPv4 and B is IPv6. A wins!
|
|
|
|
if !h[A].IPv6 && h[B].IPv6 {
|
2015-03-02 07:15:02 +00:00
|
|
|
return true
|
|
|
|
}
|
2015-03-22 00:43:27 +00:00
|
|
|
// A is IPv6 but B is IPv4. A loses!
|
|
|
|
if h[A].IPv6 && !h[B].IPv6 {
|
|
|
|
return false
|
|
|
|
}
|
2015-03-02 07:15:02 +00:00
|
|
|
|
2015-03-02 08:22:05 +00:00
|
|
|
// Compare the the IP addresses (byte array)
|
2015-03-22 01:11:09 +00:00
|
|
|
// We want to push 127. to the top so we're going to mark it zero.
|
|
|
|
surrogateA := MakeSurrogateIP(h[A].IP)
|
|
|
|
surrogateB := MakeSurrogateIP(h[B].IP)
|
|
|
|
if !surrogateA.Equal(surrogateB) {
|
|
|
|
for charIndex := range surrogateA {
|
2015-03-22 00:43:27 +00:00
|
|
|
// A and B's IPs differ at this index, and A is less. A wins!
|
2015-03-22 01:11:09 +00:00
|
|
|
if surrogateA[charIndex] < surrogateB[charIndex] {
|
2015-03-02 09:28:10 +00:00
|
|
|
return true
|
2015-03-22 00:43:27 +00:00
|
|
|
}
|
|
|
|
// A and B's IPs differ at this index, and B is less. A loses!
|
2015-03-22 01:11:09 +00:00
|
|
|
if surrogateA[charIndex] > surrogateB[charIndex] {
|
2015-03-02 09:28:10 +00:00
|
|
|
return false
|
|
|
|
}
|
2015-03-02 07:15:02 +00:00
|
|
|
}
|
2015-03-22 01:11:09 +00:00
|
|
|
// If we got here then the IPs are the same and we want to continue on
|
|
|
|
// to the domain sorting section.
|
2015-03-02 07:15:02 +00:00
|
|
|
}
|
|
|
|
|
2015-03-22 01:11:09 +00:00
|
|
|
// Prep for sorting by domain name
|
2015-03-22 00:43:27 +00:00
|
|
|
aLength := len(h[A].Domain)
|
|
|
|
bLength := len(h[B].Domain)
|
|
|
|
max := aLength
|
|
|
|
if bLength > max {
|
|
|
|
max = bLength
|
2015-03-02 07:15:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Sort domains alphabetically
|
2015-03-21 10:59:53 +00:00
|
|
|
// TODO: This works best if domains are lowercased. However, we do not
|
2015-03-02 07:15:02 +00:00
|
|
|
// enforce lowercase because of UTF-8 domain names, which may be broken by
|
2015-03-02 09:29:08 +00:00
|
|
|
// case folding. There is a way to do this correctly but it's complicated
|
|
|
|
// so I'm not going to do it right now.
|
2015-03-22 00:43:27 +00:00
|
|
|
for charIndex := 0; charIndex < max; charIndex++ {
|
|
|
|
// This index is longer than A, so A is shorter. A wins!
|
|
|
|
if charIndex >= aLength {
|
2015-03-02 07:15:02 +00:00
|
|
|
return true
|
|
|
|
}
|
2015-03-22 00:43:27 +00:00
|
|
|
// This index is longer than B, so B is shorter. A loses!
|
|
|
|
if charIndex >= bLength {
|
2015-03-02 07:15:02 +00:00
|
|
|
return false
|
|
|
|
}
|
2015-03-22 00:43:27 +00:00
|
|
|
// A and B differ at this index and A is less. A wins!
|
|
|
|
if h[A].Domain[charIndex] < h[B].Domain[charIndex] {
|
2015-03-02 07:15:02 +00:00
|
|
|
return true
|
|
|
|
}
|
2015-03-22 00:43:27 +00:00
|
|
|
// A and B differ at this index and B is less. A loses!
|
|
|
|
if h[A].Domain[charIndex] > h[B].Domain[charIndex] {
|
2015-03-02 07:15:02 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-22 01:11:09 +00:00
|
|
|
// If we got here then A and B are the same -- by definition A is not Less
|
|
|
|
// than B so we return false. Technically we shouldn't get here since Add
|
|
|
|
// should not allow duplicates, but we'll guard anyway.
|
2015-03-02 07:15:02 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2015-03-02 08:55:34 +00:00
|
|
|
// Swap changes the position of two Hostnames, part of sort.Interface
|
2015-03-02 07:15:02 +00:00
|
|
|
func (h Hostlist) Swap(i, j int) {
|
|
|
|
h[i], h[j] = h[j], h[i]
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sort this list of Hostnames, according to Hostlist sorting rules:
|
2015-03-02 10:20:34 +00:00
|
|
|
//
|
|
|
|
// 1. localhost comes before other domains
|
|
|
|
// 2. IPv4 comes before IPv6
|
|
|
|
// 3. IPs are sorted in numerical order
|
|
|
|
// 4. domains are sorted in alphabetical
|
2015-03-02 07:15:02 +00:00
|
|
|
func (h *Hostlist) Sort() {
|
|
|
|
sort.Sort(*h)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Contains returns true if this Hostlist has the specified Hostname
|
|
|
|
func (h *Hostlist) Contains(b *Hostname) bool {
|
2015-02-25 10:33:18 +00:00
|
|
|
for _, a := range *h {
|
2015-02-25 07:08:53 +00:00
|
|
|
if a.Equal(b) {
|
2015-02-24 08:55:24 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2015-03-02 07:15:02 +00:00
|
|
|
// ContainsDomain returns true if a Hostname in this Hostlist matches domain
|
2015-02-25 10:33:18 +00:00
|
|
|
func (h *Hostlist) ContainsDomain(domain string) bool {
|
|
|
|
for _, hostname := range *h {
|
2015-02-25 07:08:53 +00:00
|
|
|
if hostname.Domain == domain {
|
2015-02-24 08:55:24 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2015-03-02 08:22:05 +00:00
|
|
|
// ContainsIP returns true if a Hostname in this Hostlist matches IP
|
|
|
|
func (h *Hostlist) ContainsIP(IP net.IP) bool {
|
2015-02-25 10:33:18 +00:00
|
|
|
for _, hostname := range *h {
|
2015-03-02 08:55:34 +00:00
|
|
|
if hostname.EqualIP(IP) {
|
2015-02-24 08:55:24 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
2015-02-25 10:33:18 +00:00
|
|
|
|
2015-03-02 07:15:02 +00:00
|
|
|
// Add a new Hostname to this hostlist. If a Hostname with the same domain name
|
2015-03-02 08:22:05 +00:00
|
|
|
// and IP version is found, it will be replaced and an error will be returned.
|
2015-03-02 07:15:02 +00:00
|
|
|
// If you try to add an identical Hostname, an error will be returned.
|
|
|
|
// Note that in normal operation, you will sometimes expect an error, and the
|
|
|
|
// error data is mainly to alert you that you mis-entered information, not that
|
|
|
|
// the application has a problem.
|
2015-02-25 10:33:18 +00:00
|
|
|
func (h *Hostlist) Add(host *Hostname) error {
|
|
|
|
for _, found := range *h {
|
|
|
|
if found.Equal(host) {
|
2015-03-02 08:22:05 +00:00
|
|
|
return fmt.Errorf("Duplicate hostname entry for %s -> %s",
|
2015-03-02 08:55:34 +00:00
|
|
|
host.Domain, host.IP)
|
|
|
|
} else if found.Domain == host.Domain && found.IPv6 == host.IPv6 {
|
2015-03-02 08:22:05 +00:00
|
|
|
return fmt.Errorf("Conflicting hostname entries for %s -> %s and -> %s",
|
2015-03-02 08:55:34 +00:00
|
|
|
host.Domain, host.IP, found.IP)
|
2015-02-25 10:33:18 +00:00
|
|
|
}
|
|
|
|
}
|
2015-02-25 10:37:57 +00:00
|
|
|
*h = append(*h, host)
|
2015-02-25 10:33:18 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-03-02 07:15:02 +00:00
|
|
|
// IndexOf will indicate the index of a Hostname in Hostlist, or -1 if it is
|
|
|
|
// not found.
|
2015-02-25 10:33:18 +00:00
|
|
|
func (h *Hostlist) IndexOf(host *Hostname) int {
|
|
|
|
for index, found := range *h {
|
|
|
|
if found.Equal(host) {
|
|
|
|
return index
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
|
2015-03-02 07:15:02 +00:00
|
|
|
// IndexOfDomainV will indicate the index of a Hostname in Hostlist that has
|
|
|
|
// the same domain and IP version, or -1 if it is not found.
|
2015-03-02 10:20:34 +00:00
|
|
|
//
|
2015-03-02 07:15:02 +00:00
|
|
|
// This function will panic if IP version is not 4 or 6.
|
2015-03-02 05:54:51 +00:00
|
|
|
func (h *Hostlist) IndexOfDomainV(domain string, version int) int {
|
2015-03-02 07:15:02 +00:00
|
|
|
if version != 4 && version != 6 {
|
2015-03-02 08:22:05 +00:00
|
|
|
panic(ErrInvalidVersionArg)
|
2015-03-02 07:15:02 +00:00
|
|
|
}
|
2015-03-02 05:54:51 +00:00
|
|
|
for index, hostname := range *h {
|
2015-03-02 08:55:34 +00:00
|
|
|
if hostname.Domain == domain && hostname.IPv6 == (version == 6) {
|
2015-02-25 10:33:18 +00:00
|
|
|
return index
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
|
2015-03-02 07:15:02 +00:00
|
|
|
// Remove will delete the Hostname at the specified index. If index is out of
|
|
|
|
// bounds (i.e. -1), Remove silently no-ops.
|
2015-02-25 10:33:18 +00:00
|
|
|
func (h *Hostlist) Remove(index int) {
|
2015-03-02 05:54:51 +00:00
|
|
|
if index > -1 && index < len(*h) {
|
|
|
|
*h = append((*h)[:index], (*h)[index+1:]...)
|
|
|
|
}
|
2015-02-25 10:33:18 +00:00
|
|
|
}
|
|
|
|
|
2015-03-02 08:55:34 +00:00
|
|
|
// RemoveDomain removes both IPv4 and IPv6 Hostname entries matching domain.
|
2015-03-02 05:54:51 +00:00
|
|
|
func (h *Hostlist) RemoveDomain(domain string) {
|
|
|
|
h.Remove(h.IndexOfDomainV(domain, 4))
|
|
|
|
h.Remove(h.IndexOfDomainV(domain, 6))
|
2015-02-25 10:33:18 +00:00
|
|
|
}
|
|
|
|
|
2015-03-02 07:15:02 +00:00
|
|
|
// RemoveDomainV removes a Hostname entry matching the domain and IP version.
|
2015-03-02 10:20:34 +00:00
|
|
|
//
|
2015-03-02 07:15:02 +00:00
|
|
|
// This function will panic if IP version is not 4 or 6.
|
2015-03-02 05:54:51 +00:00
|
|
|
func (h *Hostlist) RemoveDomainV(domain string, version int) {
|
|
|
|
if version != 4 && version != 6 {
|
2015-03-02 08:22:05 +00:00
|
|
|
panic(ErrInvalidVersionArg)
|
2015-03-02 05:54:51 +00:00
|
|
|
}
|
|
|
|
h.Remove(h.IndexOfDomainV(domain, version))
|
2015-02-25 10:33:18 +00:00
|
|
|
}
|
|
|
|
|
2015-03-02 07:15:02 +00:00
|
|
|
// Enable will change any Hostnames matching domain to be enabled.
|
2015-02-25 10:33:18 +00:00
|
|
|
func (h *Hostlist) Enable(domain string) {
|
|
|
|
for _, hostname := range *h {
|
|
|
|
if hostname.Domain == domain {
|
|
|
|
hostname.Enabled = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-02 07:15:02 +00:00
|
|
|
// EnableV will change a Hostname matching domain and IP version to be enabled.
|
2015-03-02 10:20:34 +00:00
|
|
|
//
|
2015-03-02 07:15:02 +00:00
|
|
|
// This function will panic if IP version is not 4 or 6.
|
2015-03-02 05:54:51 +00:00
|
|
|
func (h *Hostlist) EnableV(domain string, version int) {
|
2015-03-02 07:15:02 +00:00
|
|
|
if version != 4 && version != 6 {
|
2015-03-02 08:22:05 +00:00
|
|
|
panic(ErrInvalidVersionArg)
|
2015-03-02 07:15:02 +00:00
|
|
|
}
|
2015-03-02 05:54:51 +00:00
|
|
|
for _, hostname := range *h {
|
2015-03-02 08:55:34 +00:00
|
|
|
if hostname.Domain == domain && hostname.IPv6 == (version == 6) {
|
2015-03-02 05:54:51 +00:00
|
|
|
hostname.Enabled = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-02 08:22:05 +00:00
|
|
|
// Disable will change any Hostnames matching domain to be disabled.
|
2015-02-25 10:33:18 +00:00
|
|
|
func (h *Hostlist) Disable(domain string) {
|
|
|
|
for _, hostname := range *h {
|
|
|
|
if hostname.Domain == domain {
|
|
|
|
hostname.Enabled = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-02 08:22:05 +00:00
|
|
|
// DisableV will change any Hostnames matching domain and IP version to be disabled.
|
2015-03-02 10:20:34 +00:00
|
|
|
//
|
2015-03-02 07:15:02 +00:00
|
|
|
// This function will panic if IP version is not 4 or 6.
|
2015-03-02 05:54:51 +00:00
|
|
|
func (h *Hostlist) DisableV(domain string, version int) {
|
2015-03-02 07:15:02 +00:00
|
|
|
if version != 4 && version != 6 {
|
2015-03-02 08:22:05 +00:00
|
|
|
panic(ErrInvalidVersionArg)
|
2015-03-02 07:15:02 +00:00
|
|
|
}
|
2015-03-02 05:54:51 +00:00
|
|
|
for _, hostname := range *h {
|
2015-03-02 08:55:34 +00:00
|
|
|
if hostname.Domain == domain && hostname.IPv6 == (version == 6) {
|
2015-03-02 05:54:51 +00:00
|
|
|
hostname.Enabled = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-22 00:07:58 +00:00
|
|
|
// FilterByIP filters the list of hostnames by IP address.
|
|
|
|
func (h *Hostlist) FilterByIP(IP net.IP) (hostnames []*Hostname) {
|
2015-03-21 10:59:53 +00:00
|
|
|
for _, hostname := range *h {
|
|
|
|
if hostname.IP.Equal(IP) {
|
2015-03-22 00:07:58 +00:00
|
|
|
hostnames = append(hostnames, hostname)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// FilterByDomain filters the list of hostnames by Domain.
|
|
|
|
func (h *Hostlist) FilterByDomain(domain string) (hostnames []*Hostname) {
|
|
|
|
for _, hostname := range *h {
|
|
|
|
if hostname.Domain == domain {
|
|
|
|
hostnames = append(hostnames, hostname)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// FilterByDomainV filters the list of hostnames by domain and IPv4 or IPv6.
|
|
|
|
// This should never contain more than one item, but returns a list for
|
|
|
|
// consistency with other filter functions.
|
|
|
|
func (h *Hostlist) FilterByDomainV(domain string, version int) (hostnames []*Hostname) {
|
|
|
|
for _, hostname := range *h {
|
|
|
|
if hostname.Domain == domain && hostname.IPv6 == (version == 6) {
|
|
|
|
hostnames = append(hostnames, hostname)
|
2015-03-21 10:59:53 +00:00
|
|
|
}
|
|
|
|
}
|
2015-03-22 00:07:58 +00:00
|
|
|
return
|
2015-03-21 10:59:53 +00:00
|
|
|
}
|
|
|
|
|
2015-03-02 02:10:01 +00:00
|
|
|
// Format takes the current list of Hostnames in this Hostfile and turns it
|
|
|
|
// into a string suitable for use as an /etc/hosts file.
|
|
|
|
// Sorting uses the following logic:
|
2015-03-02 10:20:34 +00:00
|
|
|
//
|
2015-03-02 02:10:01 +00:00
|
|
|
// 1. List is sorted by IP address
|
2015-03-02 08:55:34 +00:00
|
|
|
// 2. Commented items are sorted displayed
|
2015-03-02 02:10:01 +00:00
|
|
|
// 3. 127.* appears at the top of the list (so boot resolvers don't break)
|
2015-03-02 08:55:34 +00:00
|
|
|
// 4. When present, "localhost" will always appear first in the domain list
|
2015-03-22 00:18:54 +00:00
|
|
|
func (h *Hostlist) Format() []byte {
|
2015-03-02 07:15:02 +00:00
|
|
|
h.Sort()
|
2015-03-22 00:18:54 +00:00
|
|
|
var out []byte
|
2015-03-02 02:10:01 +00:00
|
|
|
for _, hostname := range *h {
|
2015-03-22 00:18:54 +00:00
|
|
|
out = append(out, []byte(hostname.Format())...)
|
|
|
|
out = append(out, []byte("\n")...)
|
2015-03-02 02:10:01 +00:00
|
|
|
}
|
|
|
|
return out
|
|
|
|
}
|