github.com/tonistiigi/docker@v0.10.1-0.20240229224939-974013b0dc6a/libnetwork/resolvconf/resolvconf.go (about)

     1  // Package resolvconf provides utility code to query and update DNS configuration in /etc/resolv.conf
     2  package resolvconf
     3  
     4  import (
     5  	"bytes"
     6  	"fmt"
     7  	"os"
     8  	"strings"
     9  
    10  	"github.com/docker/docker/libnetwork/internal/resolvconf"
    11  	"github.com/opencontainers/go-digest"
    12  )
    13  
    14  // constants for the IP address type
    15  const (
    16  	IP = iota // IPv4 and IPv6
    17  	IPv4
    18  	IPv6
    19  )
    20  
    21  // File contains the resolv.conf content and its hash
    22  type File struct {
    23  	Content []byte
    24  	Hash    []byte
    25  }
    26  
    27  func Path() string {
    28  	return resolvconf.Path()
    29  }
    30  
    31  // Get returns the contents of /etc/resolv.conf and its hash
    32  func Get() (*File, error) {
    33  	return GetSpecific(Path())
    34  }
    35  
    36  // GetSpecific returns the contents of the user specified resolv.conf file and its hash
    37  func GetSpecific(path string) (*File, error) {
    38  	resolv, err := os.ReadFile(path)
    39  	if err != nil {
    40  		return nil, err
    41  	}
    42  	hash := digest.FromBytes(resolv)
    43  	return &File{Content: resolv, Hash: []byte(hash)}, nil
    44  }
    45  
    46  // FilterResolvDNS cleans up the config in resolvConf.  It has two main jobs:
    47  //  1. It looks for localhost (127.*|::1) entries in the provided
    48  //     resolv.conf, removing local nameserver entries, and, if the resulting
    49  //     cleaned config has no defined nameservers left, adds default DNS entries
    50  //  2. Given the caller provides the enable/disable state of IPv6, the filter
    51  //     code will remove all IPv6 nameservers if it is not enabled for containers
    52  func FilterResolvDNS(resolvConf []byte, ipv6Enabled bool) (*File, error) {
    53  	rc, err := resolvconf.Parse(bytes.NewBuffer(resolvConf), "")
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  	rc.TransformForLegacyNw(ipv6Enabled)
    58  	content, err := rc.Generate(false)
    59  	if err != nil {
    60  		return nil, err
    61  	}
    62  	hash := digest.FromBytes(content)
    63  	return &File{Content: content, Hash: []byte(hash)}, nil
    64  }
    65  
    66  // GetNameservers returns nameservers (if any) listed in /etc/resolv.conf
    67  func GetNameservers(resolvConf []byte, kind int) []string {
    68  	rc, err := resolvconf.Parse(bytes.NewBuffer(resolvConf), "")
    69  	if err != nil {
    70  		return nil
    71  	}
    72  	nsAddrs := rc.NameServers()
    73  	var nameservers []string
    74  	for _, addr := range nsAddrs {
    75  		if kind == IP {
    76  			nameservers = append(nameservers, addr.String())
    77  		} else if kind == IPv4 && addr.Is4() {
    78  			nameservers = append(nameservers, addr.String())
    79  		} else if kind == IPv6 && addr.Is6() {
    80  			nameservers = append(nameservers, addr.String())
    81  		}
    82  	}
    83  	return nameservers
    84  }
    85  
    86  // GetNameserversAsCIDR returns nameservers (if any) listed in
    87  // /etc/resolv.conf as CIDR blocks (e.g., "1.2.3.4/32")
    88  // This function's output is intended for net.ParseCIDR
    89  func GetNameserversAsCIDR(resolvConf []byte) []string {
    90  	rc, err := resolvconf.Parse(bytes.NewBuffer(resolvConf), "")
    91  	if err != nil {
    92  		return nil
    93  	}
    94  	nsAddrs := rc.NameServers()
    95  	nameservers := make([]string, 0, len(nsAddrs))
    96  	for _, addr := range nsAddrs {
    97  		str := fmt.Sprintf("%s/%d", addr.WithZone("").String(), addr.BitLen())
    98  		nameservers = append(nameservers, str)
    99  	}
   100  	return nameservers
   101  }
   102  
   103  // GetSearchDomains returns search domains (if any) listed in /etc/resolv.conf
   104  // If more than one search line is encountered, only the contents of the last
   105  // one is returned.
   106  func GetSearchDomains(resolvConf []byte) []string {
   107  	rc, err := resolvconf.Parse(bytes.NewBuffer(resolvConf), "")
   108  	if err != nil {
   109  		return nil
   110  	}
   111  	return rc.Search()
   112  }
   113  
   114  // GetOptions returns options (if any) listed in /etc/resolv.conf
   115  // If more than one options line is encountered, only the contents of the last
   116  // one is returned.
   117  func GetOptions(resolvConf []byte) []string {
   118  	rc, err := resolvconf.Parse(bytes.NewBuffer(resolvConf), "")
   119  	if err != nil {
   120  		return nil
   121  	}
   122  	return rc.Options()
   123  }
   124  
   125  // Build generates and writes a configuration file to path containing a nameserver
   126  // entry for every element in nameservers, a "search" entry for every element in
   127  // dnsSearch, and an "options" entry for every element in dnsOptions. It returns
   128  // a File containing the generated content and its (sha256) hash.
   129  //
   130  // Note that the resolv.conf file is written, but the hash file is not.
   131  func Build(path string, nameservers, dnsSearch, dnsOptions []string) (*File, error) {
   132  	content := bytes.NewBuffer(nil)
   133  	if len(dnsSearch) > 0 {
   134  		if searchString := strings.Join(dnsSearch, " "); strings.Trim(searchString, " ") != "." {
   135  			if _, err := content.WriteString("search " + searchString + "\n"); err != nil {
   136  				return nil, err
   137  			}
   138  		}
   139  	}
   140  	for _, dns := range nameservers {
   141  		if _, err := content.WriteString("nameserver " + dns + "\n"); err != nil {
   142  			return nil, err
   143  		}
   144  	}
   145  	if len(dnsOptions) > 0 {
   146  		if optsString := strings.Join(dnsOptions, " "); strings.Trim(optsString, " ") != "" {
   147  			if _, err := content.WriteString("options " + optsString + "\n"); err != nil {
   148  				return nil, err
   149  			}
   150  		}
   151  	}
   152  
   153  	if err := os.WriteFile(path, content.Bytes(), 0o644); err != nil {
   154  		return nil, err
   155  	}
   156  
   157  	hash := digest.FromBytes(content.Bytes())
   158  	return &File{Content: content.Bytes(), Hash: []byte(hash)}, nil
   159  }