mirror of https://github.com/k3s-io/k3s
Merge pull request #41618 from JiangtianLi/k8swin
Automatic merge from submit-queue (batch tested with PRs 42316, 41618, 42201, 42113, 42191) Support unqualified and partially qualified domain name in DNS query in Windows kube-proxy **What this PR does / why we need it**: In Windows container networking, --dns-search is not currently supported on Windows Docker. Besides, even with --dns-suffix, inside Windows container DNS suffix is not appended to DNS query names. That makes unqualified domain name or partially qualified domain name in DNS query not able to resolve. This PR provides a solution to resolve unqualified domain name or partially qualified domain name in DNS query for Windows container in Windows kube-proxy. It uses well-known Kubernetes DNS suffix as well host DNS suffix search list to append to the name in DNS query. DNS packet in kube-proxy UDP stream is modified as appropriate. This PR affects the Windows kube-proxy only. **Special notes for your reviewer**: This PR is based on top of Anthony Howe's commitpull/6/head48647fb
,0e37f0a
and7e2c71f
which is already included in the PR 41487. Please only review commitb9dfb69
. **Release note**: ```release-note Add DNS suffix search list support in Windows kube-proxy. ```
commit
44a7be98fd
|
@ -22,6 +22,8 @@ go_library(
|
|||
deps = [
|
||||
"//pkg/api: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/slice:go_default_library",
|
||||
"//vendor:github.com/golang/glog",
|
||||
|
@ -35,6 +37,7 @@ go_test(
|
|||
name = "go_default_test",
|
||||
srcs = [
|
||||
"proxier_test.go",
|
||||
"proxysocket_test.go",
|
||||
"roundrobin_test.go",
|
||||
],
|
||||
library = ":go_default_library",
|
||||
|
|
|
@ -50,6 +50,7 @@ type serviceInfo struct {
|
|||
socket proxySocket
|
||||
timeout time.Duration
|
||||
activeClients *clientCache
|
||||
dnsClients *dnsClientCache
|
||||
sessionAffinityType api.ServiceAffinity
|
||||
}
|
||||
|
||||
|
@ -260,6 +261,7 @@ func (proxier *Proxier) addServicePortPortal(servicePortPortalName ServicePortPo
|
|||
socket: sock,
|
||||
timeout: timeout,
|
||||
activeClients: newClientCache(),
|
||||
dnsClients: newDnsClientCache(),
|
||||
sessionAffinityType: api.ServiceAffinityNone, // default
|
||||
}
|
||||
proxier.setServiceInfo(servicePortPortalName, si)
|
||||
|
|
|
@ -17,12 +17,14 @@ limitations under the License.
|
|||
package winuserspace
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
@ -30,6 +32,36 @@ import (
|
|||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"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.
|
||||
|
@ -205,8 +237,399 @@ func newClientCache() *clientCache {
|
|||
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) {
|
||||
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 {
|
||||
if !myInfo.isAlive() {
|
||||
// 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)
|
||||
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.
|
||||
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 {
|
||||
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()
|
||||
defer activeClients.mu.Unlock()
|
||||
|
||||
|
@ -268,17 +697,17 @@ func (udp *udpProxySocket) getBackendConn(activeClients *clientCache, cliAddr ne
|
|||
return nil, err
|
||||
}
|
||||
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()
|
||||
udp.proxyClient(cliAddr, svrConn, activeClients, timeout)
|
||||
}(cliAddr, svrConn, activeClients, timeout)
|
||||
udp.proxyClient(cliAddr, svrConn, activeClients, dnsClients, service, timeout, dnsSearch)
|
||||
}(cliAddr, svrConn, activeClients, dnsClients, service, timeout, dnsSearch)
|
||||
}
|
||||
return svrConn, nil
|
||||
}
|
||||
|
||||
// This function is expected to be called as a goroutine.
|
||||
// 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()
|
||||
var buffer [4096]byte
|
||||
for {
|
||||
|
@ -289,17 +718,25 @@ func (udp *udpProxySocket) proxyClient(cliAddr net.Addr, svrConn net.Conn, activ
|
|||
}
|
||||
break
|
||||
}
|
||||
err = svrConn.SetDeadline(time.Now().Add(timeout))
|
||||
if err != nil {
|
||||
glog.Errorf("SetDeadline failed: %v", err)
|
||||
break
|
||||
|
||||
drop := false
|
||||
if isDnsService(service.Port) {
|
||||
drop = processDnsResponsePacket(svrConn, dnsClients, cliAddr, buffer[:], n, dnsSearch)
|
||||
}
|
||||
n, err = udp.WriteTo(buffer[0:n], cliAddr)
|
||||
if err != nil {
|
||||
if !logTimeout(err) {
|
||||
glog.Errorf("WriteTo failed: %v", err)
|
||||
|
||||
if !drop {
|
||||
err = svrConn.SetDeadline(time.Now().Add(timeout))
|
||||
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()
|
||||
|
|
|
@ -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.")
|
||||
}
|
||||
}
|
|
@ -65,6 +65,7 @@ filegroup(
|
|||
"//pkg/util/interrupt:all-srcs",
|
||||
"//pkg/util/intstr:all-srcs",
|
||||
"//pkg/util/io:all-srcs",
|
||||
"//pkg/util/ipconfig:all-srcs",
|
||||
"//pkg/util/iptables:all-srcs",
|
||||
"//pkg/util/json:all-srcs",
|
||||
"//pkg/util/keymutex:all-srcs",
|
||||
|
|
|
@ -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"],
|
||||
)
|
|
@ -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"
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue