Fix DNS suffix search list issue for Windows container and workaround in kube-proxy.

kube-proxy iterates over DNS suffix search list and appends to DNS query for client.
pull/6/head
Jiangtian Li 2017-02-11 15:19:40 -08:00
parent 7e2c71f698
commit b9dfb69dd7
9 changed files with 780 additions and 15 deletions

View File

@ -22,6 +22,8 @@ go_library(
deps = [ deps = [
"//pkg/api:go_default_library", "//pkg/api:go_default_library",
"//pkg/proxy:go_default_library", "//pkg/proxy:go_default_library",
"//pkg/util/exec:go_default_library",
"//pkg/util/ipconfig:go_default_library",
"//pkg/util/netsh:go_default_library", "//pkg/util/netsh:go_default_library",
"//pkg/util/slice:go_default_library", "//pkg/util/slice:go_default_library",
"//vendor:github.com/golang/glog", "//vendor:github.com/golang/glog",
@ -35,6 +37,7 @@ go_test(
name = "go_default_test", name = "go_default_test",
srcs = [ srcs = [
"proxier_test.go", "proxier_test.go",
"proxysocket_test.go",
"roundrobin_test.go", "roundrobin_test.go",
], ],
library = ":go_default_library", library = ":go_default_library",

View File

@ -50,6 +50,7 @@ type serviceInfo struct {
socket proxySocket socket proxySocket
timeout time.Duration timeout time.Duration
activeClients *clientCache activeClients *clientCache
dnsClients *dnsClientCache
sessionAffinityType api.ServiceAffinity sessionAffinityType api.ServiceAffinity
} }
@ -260,6 +261,7 @@ func (proxier *Proxier) addServicePortPortal(servicePortPortalName ServicePortPo
socket: sock, socket: sock,
timeout: timeout, timeout: timeout,
activeClients: newClientCache(), activeClients: newClientCache(),
dnsClients: newDnsClientCache(),
sessionAffinityType: api.ServiceAffinityNone, // default sessionAffinityType: api.ServiceAffinityNone, // default
} }
proxier.setServiceInfo(servicePortPortalName, si) proxier.setServiceInfo(servicePortPortalName, si)

View File

@ -17,12 +17,14 @@ limitations under the License.
package winuserspace package winuserspace
import ( import (
"encoding/binary"
"fmt" "fmt"
"io" "io"
"net" "net"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"sync/atomic"
"time" "time"
"github.com/golang/glog" "github.com/golang/glog"
@ -30,6 +32,36 @@ import (
"k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/proxy" "k8s.io/kubernetes/pkg/proxy"
"k8s.io/kubernetes/pkg/util/exec"
"k8s.io/kubernetes/pkg/util/ipconfig"
)
const (
// Kubernetes DNS suffix search list
// TODO: Get DNS suffix search list from docker containers.
// --dns-search option doesn't work on Windows containers and has been
// fixed recently in docker.
// Kubernetes cluster domain
clusterDomain = "cluster.local"
// Kubernetes service domain
serviceDomain = "svc." + clusterDomain
// Kubernetes default namespace domain
namespaceServiceDomain = "default." + serviceDomain
// Kubernetes DNS service port name
dnsPortName = "dns"
// DNS TYPE value A (a host address)
dnsTypeA uint16 = 0x01
// DNS TYPE value AAAA (a host IPv6 address)
dnsTypeAAAA uint16 = 0x1c
// DNS CLASS value IN (the Internet)
dnsClassInternet uint16 = 0x01
) )
// Abstraction over TCP/UDP sockets which are proxied. // Abstraction over TCP/UDP sockets which are proxied.
@ -205,8 +237,399 @@ func newClientCache() *clientCache {
return &clientCache{clients: map[string]net.Conn{}} return &clientCache{clients: map[string]net.Conn{}}
} }
// TODO: use Go net dnsmsg library to walk DNS message format
// DNS packet header
type dnsHeader struct {
id uint16
bits uint16
qdCount uint16
anCount uint16
nsCount uint16
arCount uint16
}
// DNS domain name
type dnsDomainName struct {
name string
}
// DNS packet question section
type dnsQuestion struct {
qName dnsDomainName
qType uint16
qClass uint16
}
// DNS message, only interested in question now
type dnsMsg struct {
header dnsHeader
question []dnsQuestion
}
type dnsStruct interface {
walk(f func(field interface{}) (ok bool)) (ok bool)
}
func (header *dnsHeader) walk(f func(field interface{}) bool) bool {
return f(&header.id) &&
f(&header.bits) &&
f(&header.qdCount) &&
f(&header.anCount) &&
f(&header.nsCount) &&
f(&header.arCount)
}
func (question *dnsQuestion) walk(f func(field interface{}) bool) bool {
return f(&question.qName) &&
f(&question.qType) &&
f(&question.qClass)
}
func packDomainName(name string, buffer []byte, index int) (newIndex int, ok bool) {
if name == "" {
buffer[index] = 0
index++
return index, true
}
// one more dot plus trailing 0
if index+len(name)+2 > len(buffer) {
return len(buffer), false
}
domains := strings.Split(name, ".")
for _, domain := range domains {
domainLen := len(domain)
if domainLen == 0 {
return len(buffer), false
}
buffer[index] = byte(domainLen)
index++
copy(buffer[index:index+domainLen], domain)
index += domainLen
}
buffer[index] = 0
index++
return index, true
}
func unpackDomainName(buffer []byte, index int) (name string, newIndex int, ok bool) {
name = ""
for index < len(buffer) {
cnt := int(buffer[index])
index++
if cnt == 0 {
break
}
if index+cnt > len(buffer) {
return "", len(buffer), false
}
if name != "" {
name += "."
}
name += string(buffer[index : index+cnt])
index += cnt
}
if index >= len(buffer) {
return "", len(buffer), false
}
return name, index, true
}
func packStruct(any dnsStruct, buffer []byte, index int) (newIndex int, ok bool) {
ok = any.walk(func(field interface{}) bool {
switch value := field.(type) {
case *uint16:
if index+2 > len(buffer) {
return false
}
binary.BigEndian.PutUint16(buffer[index:index+2], *value)
index += 2
return true
case *dnsDomainName:
index, ok = packDomainName((*value).name, buffer, index)
return ok
default:
return false
}
})
if !ok {
return len(buffer), false
}
return index, true
}
func unpackStruct(any dnsStruct, buffer []byte, index int) (newIndex int, ok bool) {
ok = any.walk(func(field interface{}) bool {
switch value := field.(type) {
case *uint16:
if index+2 > len(buffer) {
return false
}
*value = binary.BigEndian.Uint16(buffer[index : index+2])
index += 2
return true
case *dnsDomainName:
(*value).name, index, ok = unpackDomainName(buffer, index)
return ok
default:
return false
}
})
if !ok {
return len(buffer), false
}
return index, true
}
// Pack the message structure into buffer
func (msg *dnsMsg) packDnsMsg(buffer []byte) (length int, ok bool) {
index := 0
if index, ok = packStruct(&msg.header, buffer, index); !ok {
return len(buffer), false
}
for i := 0; i < len(msg.question); i++ {
if index, ok = packStruct(&msg.question[i], buffer, index); !ok {
return len(buffer), false
}
}
return index, true
}
// Unpack the buffer into the message structure
func (msg *dnsMsg) unpackDnsMsg(buffer []byte) (ok bool) {
index := 0
if index, ok = unpackStruct(&msg.header, buffer, index); !ok {
return false
}
msg.question = make([]dnsQuestion, msg.header.qdCount)
for i := 0; i < len(msg.question); i++ {
if index, ok = unpackStruct(&msg.question[i], buffer, index); !ok {
return false
}
}
return true
}
// DNS query client classified by address and QTYPE
type dnsClientQuery struct {
clientAddress string
dnsQType uint16
}
// Holds DNS client query, the value contains the index in DNS suffix search list,
// the original DNS message and length for the same client and QTYPE
type dnsClientCache struct {
mu sync.Mutex
clients map[dnsClientQuery]*dnsQueryState
}
type dnsQueryState struct {
searchIndex int32
msg *dnsMsg
}
func newDnsClientCache() *dnsClientCache {
return &dnsClientCache{clients: map[dnsClientQuery]*dnsQueryState{}}
}
func packetRequiresDnsSuffix(dnsType, dnsClass uint16) bool {
return (dnsType == dnsTypeA || dnsType == dnsTypeAAAA) && dnsClass == dnsClassInternet
}
func isDnsService(portName string) bool {
return portName == dnsPortName
}
func appendDnsSuffix(msg *dnsMsg, buffer []byte, length int, dnsSuffix string) int {
if msg == nil || len(msg.question) == 0 {
glog.Warning("DNS message parameter is invalid.")
return length
}
// Save the original name since it will be reused for next iteration
origName := msg.question[0].qName.name
if dnsSuffix != "" {
msg.question[0].qName.name += "." + dnsSuffix
}
len, ok := msg.packDnsMsg(buffer)
msg.question[0].qName.name = origName
if !ok {
glog.Warning("Unable to pack DNS packet.")
return length
}
return len
}
func processUnpackedDnsQueryPacket(dnsClients *dnsClientCache, msg *dnsMsg, host string, dnsQType uint16, buffer []byte, length int, dnsSearch []string) int {
if dnsSearch == nil || len(dnsSearch) == 0 {
glog.V(1).Infof("DNS search list is not initialized and is empty.")
return length
}
// TODO: handle concurrent queries from a client
dnsClients.mu.Lock()
state, found := dnsClients.clients[dnsClientQuery{host, dnsQType}]
if !found {
state = &dnsQueryState{0, msg}
dnsClients.clients[dnsClientQuery{host, dnsQType}] = state
}
dnsClients.mu.Unlock()
index := atomic.SwapInt32(&state.searchIndex, state.searchIndex+1)
// Also update message ID if the client retries due to previous query time out
state.msg.header.id = msg.header.id
if index < 0 || index >= int32(len(dnsSearch)) {
glog.V(1).Infof("Search index %d is out of range.", index)
return length
}
length = appendDnsSuffix(msg, buffer, length, dnsSearch[index])
return length
}
func processUnpackedDnsResponsePacket(svrConn net.Conn, dnsClients *dnsClientCache, rcode uint16, host string, dnsQType uint16, buffer []byte, length int, dnsSearch []string) bool {
var drop bool
if dnsSearch == nil || len(dnsSearch) == 0 {
glog.V(1).Infof("DNS search list is not initialized and is empty.")
return drop
}
dnsClients.mu.Lock()
state, found := dnsClients.clients[dnsClientQuery{host, dnsQType}]
dnsClients.mu.Unlock()
if found {
index := atomic.SwapInt32(&state.searchIndex, state.searchIndex+1)
if rcode != 0 && index >= 0 && index < int32(len(dnsSearch)) {
// If the reponse has failure and iteration through the search list has not
// reached the end, retry on behalf of the client using the original query message
drop = true
length = appendDnsSuffix(state.msg, buffer, length, dnsSearch[index])
_, err := svrConn.Write(buffer[0:length])
if err != nil {
if !logTimeout(err) {
glog.Errorf("Write failed: %v", err)
}
}
} else {
dnsClients.mu.Lock()
delete(dnsClients.clients, dnsClientQuery{host, dnsQType})
dnsClients.mu.Unlock()
}
}
return drop
}
func processDnsQueryPacket(dnsClients *dnsClientCache, cliAddr net.Addr, buffer []byte, length int, dnsSearch []string) int {
msg := &dnsMsg{}
if !msg.unpackDnsMsg(buffer[:length]) {
glog.Warning("Unable to unpack DNS packet.")
return length
}
// Query - Response bit that specifies whether this message is a query (0) or a response (1).
qr := msg.header.bits & 0x8000
if qr != 0 {
glog.Warning("DNS packet should be a query message.")
return length
}
// QDCOUNT
if msg.header.qdCount != 1 {
glog.V(1).Infof("Number of entries in the question section of the DNS packet is: %d", msg.header.qdCount)
glog.V(1).Infof("DNS suffix appending does not support more than one question.")
return length
}
// ANCOUNT, NSCOUNT, ARCOUNT
if msg.header.anCount != 0 || msg.header.nsCount != 0 || msg.header.arCount != 0 {
glog.V(1).Infof("DNS packet contains more than question section.")
return length
}
dnsQType := msg.question[0].qType
dnsQClass := msg.question[0].qClass
if packetRequiresDnsSuffix(dnsQType, dnsQClass) {
host, _, err := net.SplitHostPort(cliAddr.String())
if err != nil {
glog.V(1).Infof("Failed to get host from client address: %v", err)
host = cliAddr.String()
}
length = processUnpackedDnsQueryPacket(dnsClients, msg, host, dnsQType, buffer, length, dnsSearch)
}
return length
}
func processDnsResponsePacket(svrConn net.Conn, dnsClients *dnsClientCache, cliAddr net.Addr, buffer []byte, length int, dnsSearch []string) bool {
var drop bool
msg := &dnsMsg{}
if !msg.unpackDnsMsg(buffer[:length]) {
glog.Warning("Unable to unpack DNS packet.")
return drop
}
// Query - Response bit that specifies whether this message is a query (0) or a response (1).
qr := msg.header.bits & 0x8000
if qr == 0 {
glog.Warning("DNS packet should be a response message.")
return drop
}
// QDCOUNT
if msg.header.qdCount != 1 {
glog.V(1).Infof("Number of entries in the reponse section of the DNS packet is: %d", msg.header.qdCount)
return drop
}
dnsQType := msg.question[0].qType
dnsQClass := msg.question[0].qClass
if packetRequiresDnsSuffix(dnsQType, dnsQClass) {
host, _, err := net.SplitHostPort(cliAddr.String())
if err != nil {
glog.V(1).Infof("Failed to get host from client address: %v", err)
host = cliAddr.String()
}
rcode := msg.header.bits & 0xf
drop = processUnpackedDnsResponsePacket(svrConn, dnsClients, rcode, host, dnsQType, buffer, length, dnsSearch)
}
return drop
}
func (udp *udpProxySocket) ProxyLoop(service ServicePortPortalName, myInfo *serviceInfo, proxier *Proxier) { func (udp *udpProxySocket) ProxyLoop(service ServicePortPortalName, myInfo *serviceInfo, proxier *Proxier) {
var buffer [4096]byte // 4KiB should be enough for most whole-packets var buffer [4096]byte // 4KiB should be enough for most whole-packets
var dnsSearch []string
if isDnsService(service.Port) {
dnsSearch = []string{"", namespaceServiceDomain, serviceDomain, clusterDomain}
execer := exec.New()
ipconfigInterface := ipconfig.New(execer)
suffixList, err := ipconfigInterface.GetDnsSuffixSearchList()
if err == nil {
for _, suffix := range suffixList {
dnsSearch = append(dnsSearch, suffix)
}
}
}
for { for {
if !myInfo.isAlive() { if !myInfo.isAlive() {
// The service port was closed or replaced. // The service port was closed or replaced.
@ -226,8 +649,14 @@ func (udp *udpProxySocket) ProxyLoop(service ServicePortPortalName, myInfo *serv
glog.Errorf("ReadFrom failed, exiting ProxyLoop: %v", err) glog.Errorf("ReadFrom failed, exiting ProxyLoop: %v", err)
break break
} }
// If this is DNS query packet
if isDnsService(service.Port) {
n = processDnsQueryPacket(myInfo.dnsClients, cliAddr, buffer[:], n, dnsSearch)
}
// If this is a client we know already, reuse the connection and goroutine. // If this is a client we know already, reuse the connection and goroutine.
svrConn, err := udp.getBackendConn(myInfo.activeClients, cliAddr, proxier, service, myInfo.timeout) svrConn, err := udp.getBackendConn(myInfo.activeClients, myInfo.dnsClients, cliAddr, proxier, service, myInfo.timeout, dnsSearch)
if err != nil { if err != nil {
continue continue
} }
@ -249,7 +678,7 @@ func (udp *udpProxySocket) ProxyLoop(service ServicePortPortalName, myInfo *serv
} }
} }
func (udp *udpProxySocket) getBackendConn(activeClients *clientCache, cliAddr net.Addr, proxier *Proxier, service ServicePortPortalName, timeout time.Duration) (net.Conn, error) { func (udp *udpProxySocket) getBackendConn(activeClients *clientCache, dnsClients *dnsClientCache, cliAddr net.Addr, proxier *Proxier, service ServicePortPortalName, timeout time.Duration, dnsSearch []string) (net.Conn, error) {
activeClients.mu.Lock() activeClients.mu.Lock()
defer activeClients.mu.Unlock() defer activeClients.mu.Unlock()
@ -268,17 +697,17 @@ func (udp *udpProxySocket) getBackendConn(activeClients *clientCache, cliAddr ne
return nil, err return nil, err
} }
activeClients.clients[cliAddr.String()] = svrConn activeClients.clients[cliAddr.String()] = svrConn
go func(cliAddr net.Addr, svrConn net.Conn, activeClients *clientCache, timeout time.Duration) { go func(cliAddr net.Addr, svrConn net.Conn, activeClients *clientCache, dnsClients *dnsClientCache, service ServicePortPortalName, timeout time.Duration, dnsSearch []string) {
defer runtime.HandleCrash() defer runtime.HandleCrash()
udp.proxyClient(cliAddr, svrConn, activeClients, timeout) udp.proxyClient(cliAddr, svrConn, activeClients, dnsClients, service, timeout, dnsSearch)
}(cliAddr, svrConn, activeClients, timeout) }(cliAddr, svrConn, activeClients, dnsClients, service, timeout, dnsSearch)
} }
return svrConn, nil return svrConn, nil
} }
// This function is expected to be called as a goroutine. // This function is expected to be called as a goroutine.
// TODO: Track and log bytes copied, like TCP // TODO: Track and log bytes copied, like TCP
func (udp *udpProxySocket) proxyClient(cliAddr net.Addr, svrConn net.Conn, activeClients *clientCache, timeout time.Duration) { func (udp *udpProxySocket) proxyClient(cliAddr net.Addr, svrConn net.Conn, activeClients *clientCache, dnsClients *dnsClientCache, service ServicePortPortalName, timeout time.Duration, dnsSearch []string) {
defer svrConn.Close() defer svrConn.Close()
var buffer [4096]byte var buffer [4096]byte
for { for {
@ -289,17 +718,25 @@ func (udp *udpProxySocket) proxyClient(cliAddr net.Addr, svrConn net.Conn, activ
} }
break break
} }
err = svrConn.SetDeadline(time.Now().Add(timeout))
if err != nil { drop := false
glog.Errorf("SetDeadline failed: %v", err) if isDnsService(service.Port) {
break drop = processDnsResponsePacket(svrConn, dnsClients, cliAddr, buffer[:], n, dnsSearch)
} }
n, err = udp.WriteTo(buffer[0:n], cliAddr)
if err != nil { if !drop {
if !logTimeout(err) { err = svrConn.SetDeadline(time.Now().Add(timeout))
glog.Errorf("WriteTo failed: %v", err) if err != nil {
glog.Errorf("SetDeadline failed: %v", err)
break
}
n, err = udp.WriteTo(buffer[0:n], cliAddr)
if err != nil {
if !logTimeout(err) {
glog.Errorf("WriteTo failed: %v", err)
}
break
} }
break
} }
} }
activeClients.mu.Lock() activeClients.mu.Lock()

View File

@ -0,0 +1,129 @@
/*
Copyright 2017 The Kubernetes 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 winuserspace
import (
"reflect"
"testing"
)
func TestPackUnpackDnsMsgUnqualifiedName(t *testing.T) {
msg := &dnsMsg{}
var buffer [4096]byte
msg.header.id = 1
msg.header.qdCount = 1
msg.question = make([]dnsQuestion, msg.header.qdCount)
msg.question[0].qClass = 0x01
msg.question[0].qType = 0x01
msg.question[0].qName.name = "kubernetes"
length, ok := msg.packDnsMsg(buffer[:])
if !ok {
t.Errorf("Pack DNS message failed.")
}
unpackedMsg := &dnsMsg{}
if !unpackedMsg.unpackDnsMsg(buffer[:length]) {
t.Errorf("Unpack DNS message failed.")
}
if !reflect.DeepEqual(msg, unpackedMsg) {
t.Errorf("Pack and Unpack DNS message are not consistent.")
}
}
func TestPackUnpackDnsMsgFqdn(t *testing.T) {
msg := &dnsMsg{}
var buffer [4096]byte
msg.header.id = 1
msg.header.qdCount = 1
msg.question = make([]dnsQuestion, msg.header.qdCount)
msg.question[0].qClass = 0x01
msg.question[0].qType = 0x01
msg.question[0].qName.name = "kubernetes.default.svc.cluster.local"
length, ok := msg.packDnsMsg(buffer[:])
if !ok {
t.Errorf("Pack DNS message failed.")
}
unpackedMsg := &dnsMsg{}
if !unpackedMsg.unpackDnsMsg(buffer[:length]) {
t.Errorf("Unpack DNS message failed.")
}
if !reflect.DeepEqual(msg, unpackedMsg) {
t.Errorf("Pack and Unpack DNS message are not consistent.")
}
}
func TestPackUnpackDnsMsgEmptyName(t *testing.T) {
msg := &dnsMsg{}
var buffer [4096]byte
msg.header.id = 1
msg.header.qdCount = 1
msg.question = make([]dnsQuestion, msg.header.qdCount)
msg.question[0].qClass = 0x01
msg.question[0].qType = 0x01
msg.question[0].qName.name = ""
length, ok := msg.packDnsMsg(buffer[:])
if !ok {
t.Errorf("Pack DNS message failed.")
}
unpackedMsg := &dnsMsg{}
if !unpackedMsg.unpackDnsMsg(buffer[:length]) {
t.Errorf("Unpack DNS message failed.")
}
if !reflect.DeepEqual(msg, unpackedMsg) {
t.Errorf("Pack and Unpack DNS message are not consistent.")
}
}
func TestPackUnpackDnsMsgMultipleQuestions(t *testing.T) {
msg := &dnsMsg{}
var buffer [4096]byte
msg.header.id = 1
msg.header.qdCount = 2
msg.question = make([]dnsQuestion, msg.header.qdCount)
msg.question[0].qClass = 0x01
msg.question[0].qType = 0x01
msg.question[0].qName.name = "kubernetes"
msg.question[1].qClass = 0x01
msg.question[1].qType = 0x1c
msg.question[1].qName.name = "kubernetes.default"
length, ok := msg.packDnsMsg(buffer[:])
if !ok {
t.Errorf("Pack DNS message failed.")
}
unpackedMsg := &dnsMsg{}
if !unpackedMsg.unpackDnsMsg(buffer[:length]) {
t.Errorf("Unpack DNS message failed.")
}
if !reflect.DeepEqual(msg, unpackedMsg) {
t.Errorf("Pack and Unpack DNS message are not consistent.")
}
}

View File

@ -65,6 +65,7 @@ filegroup(
"//pkg/util/interrupt:all-srcs", "//pkg/util/interrupt:all-srcs",
"//pkg/util/intstr:all-srcs", "//pkg/util/intstr:all-srcs",
"//pkg/util/io:all-srcs", "//pkg/util/io:all-srcs",
"//pkg/util/ipconfig:all-srcs",
"//pkg/util/iptables:all-srcs", "//pkg/util/iptables:all-srcs",
"//pkg/util/json:all-srcs", "//pkg/util/json:all-srcs",
"//pkg/util/keymutex:all-srcs", "//pkg/util/keymutex:all-srcs",

43
pkg/util/ipconfig/BUILD Normal file
View File

@ -0,0 +1,43 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"ipconfig.go",
],
tags = ["automanaged"],
deps = [
"//pkg/util/exec:go_default_library",
"//vendor:github.com/golang/glog",
],
)
go_test(
name = "go_default_test",
srcs = ["ipconfig_test.go"],
library = ":go_default_library",
tags = ["automanaged"],
deps = ["//pkg/util/exec:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

18
pkg/util/ipconfig/doc.go Normal file
View File

@ -0,0 +1,18 @@
/*
Copyright 2016 The Kubernetes 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 ipconfig provides an interface and implementations for running Windows ipconfig commands.
package ipconfig // import "k8s.io/kubernetes/pkg/util/ipconfig"

View File

@ -0,0 +1,99 @@
/*
Copyright 2016 The Kubernetes 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 ipconfig
import (
"runtime"
"strings"
"sync"
"github.com/golang/glog"
utilexec "k8s.io/kubernetes/pkg/util/exec"
)
// Interface is an injectable interface for running ipconfig commands. Implementations must be goroutine-safe.
type Interface interface {
// GetDnsSuffixSearchList returns the list of DNS suffix to search
GetDnsSuffixSearchList() ([]string, error)
}
const (
cmdIpconfig string = "ipconfig"
cmdDefaultArgs string = "/all"
dnsSuffixSearchLisLabel string = "DNS Suffix Search List"
)
// runner implements Interface in terms of exec("ipconfig").
type runner struct {
mu sync.Mutex
exec utilexec.Interface
}
// New returns a new Interface which will exec ipconfig.
func New(exec utilexec.Interface) Interface {
runner := &runner{
exec: exec,
}
return runner
}
// GetDnsSuffixSearchList returns the list of DNS suffix to search
func (runner *runner) GetDnsSuffixSearchList() ([]string, error) {
// Parse the DNS suffix search list from ipconfig output
// ipconfig /all on Windows displays the entry of DNS suffix search list
// An example output contains:
//
// DNS Suffix Search List. . . . . . : example1.com
// example2.com
//
// TODO: this does not work when the label is localized
suffixList := []string{}
if runtime.GOOS != "windows" {
glog.V(1).Infof("ipconfig not supported on GOOS=%s", runtime.GOOS)
return suffixList, nil
}
out, err := runner.exec.Command(cmdIpconfig, cmdDefaultArgs).Output()
if err == nil {
lines := strings.Split(string(out), "\n")
for i, line := range lines {
if trimmed := strings.TrimSpace(line); strings.HasPrefix(trimmed, dnsSuffixSearchLisLabel) {
if parts := strings.Split(trimmed, ":"); len(parts) > 1 {
if trimmed := strings.TrimSpace(parts[1]); trimmed != "" {
suffixList = append(suffixList, strings.TrimSpace(parts[1]))
}
for j := i + 1; j < len(lines); j++ {
if trimmed := strings.TrimSpace(lines[j]); trimmed != "" && !strings.Contains(trimmed, ":") {
suffixList = append(suffixList, trimmed)
} else {
break
}
}
}
break
}
}
} else {
glog.V(1).Infof("Running %s %s failed: %v", cmdIpconfig, cmdDefaultArgs, err)
}
return suffixList, err
}

View File

@ -0,0 +1,33 @@
/*
Copyright 2016 The Kubernetes 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 ipconfig
import (
"testing"
"k8s.io/kubernetes/pkg/util/exec"
)
func TestGetDnsSuffixSearchList(t *testing.T) {
// Simple test
ipconfigInterface := New(exec.New())
_, err := ipconfigInterface.GetDnsSuffixSearchList()
if err != nil {
t.Errorf("expected success, got %v", err)
}
}