feat: add connect lite mode and optimize dns

This commit is contained in:
fengcaiwen
2023-10-26 13:03:57 +08:00
committed by naison
parent d3640ec2d1
commit b25849657d
20 changed files with 293 additions and 215 deletions

View File

@@ -1,81 +0,0 @@
package cmds
import (
"fmt"
"os"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
"github.com/wencaiwulue/kubevpn/pkg/config"
"github.com/wencaiwulue/kubevpn/pkg/handler"
"github.com/wencaiwulue/kubevpn/pkg/util"
)
func CmdConnectFork(f cmdutil.Factory) *cobra.Command {
var connect = &handler.ConnectOptions{}
var sshConf = &util.SshConfig{}
var transferImage bool
cmd := &cobra.Command{
Hidden: true,
Use: "connect-fork",
Short: i18n.T("Connect to kubernetes cluster network"),
Long: templates.LongDesc(i18n.T(`Connect to kubernetes cluster network`)),
Example: templates.Examples(i18n.T(`
# Connect to k8s cluster network
kubevpn connect
# Connect to api-server behind of bastion host or ssh jump host
kubevpn connect --ssh-addr 192.168.1.100:22 --ssh-username root --ssh-keyfile /Users/naison/.ssh/ssh.pem
kubevpn connect --ssh-addr 192.168.1.100:22 --ssh-username root --ssh-keyfile ~/.ssh/ssh.pem
# it also support ProxyJump, like
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌────────────┐
│ pc ├────►│ ssh1 ├────►│ ssh2 ├────►│ ssh3 ├─────►... ─────► │ api-server │
└──────┘ └──────┘ └──────┘ └──────┘ └────────────┘
kubevpn connect --ssh-alias <alias>
`)),
PreRunE: func(cmd *cobra.Command, args []string) (err error) {
util.InitLogger(false)
if transferImage {
err = util.TransferImage(cmd.Context(), sshConf, config.OriginImage, config.Image, os.Stdout)
if err != nil {
return err
}
}
return handler.SshJumpAndSetEnv(cmd.Context(), sshConf, cmd.Flags(), true)
},
RunE: func(cmd *cobra.Command, args []string) error {
if err := connect.InitClient(f); err != nil {
return err
}
_, err := connect.RentInnerIP(cmd.Context())
if err != nil {
return err
}
err = connect.DoConnect(cmd.Context())
defer connect.Cleanup()
if err != nil {
log.Errorln(err)
} else {
util.Print(os.Stdout, "Now you can access resources in the kubernetes cluster, enjoy it :)")
}
<-cmd.Context().Done()
return nil
},
}
cmd.Flags().BoolVar(&config.Debug, "debug", false, "enable debug mode or not, true or false")
cmd.Flags().StringVar(&config.Image, "image", config.Image, "use this image to startup container")
cmd.Flags().StringArrayVar(&connect.ExtraCIDR, "extra-cidr", []string{}, "Extra cidr string, eg: --extra-cidr 192.168.0.159/24 --extra-cidr 192.168.1.160/32")
cmd.Flags().StringArrayVar(&connect.ExtraDomain, "extra-domain", []string{}, "Extra domain string, the resolved ip will add to route table, eg: --extra-domain test.abc.com --extra-domain foo.test.com")
cmd.Flags().BoolVar(&transferImage, "transfer-image", false, "transfer image to remote registry, it will transfer image "+config.OriginImage+" to flags `--image` special image, default: "+config.Image)
cmd.Flags().BoolVar(&connect.UseLocalDNS, "use-localdns", false, "if use-lcoaldns is true, kubevpn will start coredns listen at 53 to forward your dns queries. only support on linux now")
cmd.Flags().StringVar((*string)(&connect.Engine), "engine", string(config.EngineRaw), fmt.Sprintf(`transport engine ("%s"|"%s") %s: use gvisor and raw both (both performance and stable), %s: use raw mode (best stable)`, config.EngineMix, config.EngineRaw, config.EngineMix, config.EngineRaw))
addSshFlags(cmd, sshConf)
return cmd
}

View File

@@ -23,7 +23,7 @@ import (
func CmdConnect(f cmdutil.Factory) *cobra.Command { func CmdConnect(f cmdutil.Factory) *cobra.Command {
var connect = &handler.ConnectOptions{} var connect = &handler.ConnectOptions{}
var sshConf = &util.SshConfig{} var sshConf = &util.SshConfig{}
var transferImage, foreground bool var transferImage, foreground, lite bool
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "connect", Use: "connect",
Short: i18n.T("Connect to kubernetes cluster network"), Short: i18n.T("Connect to kubernetes cluster network"),
@@ -66,8 +66,8 @@ func CmdConnect(f cmdutil.Factory) *cobra.Command {
Level: int32(log.DebugLevel), Level: int32(log.DebugLevel),
} }
// if is foreground, send to sudo daemon server // if is foreground, send to sudo daemon server
if foreground { cli := daemon.GetClient(false)
cli := daemon.GetClient(true) if lite {
resp, err := cli.ConnectFork(cmd.Context(), req) resp, err := cli.ConnectFork(cmd.Context(), req)
if err != nil { if err != nil {
return err return err
@@ -84,7 +84,6 @@ func CmdConnect(f cmdutil.Factory) *cobra.Command {
fmt.Fprint(os.Stdout, recv.GetMessage()) fmt.Fprint(os.Stdout, recv.GetMessage())
} }
} else { } else {
cli := daemon.GetClient(false)
resp, err := cli.Connect(cmd.Context(), req) resp, err := cli.Connect(cmd.Context(), req)
if err != nil { if err != nil {
return err return err
@@ -100,6 +99,8 @@ func CmdConnect(f cmdutil.Factory) *cobra.Command {
} }
fmt.Fprint(os.Stdout, recv.GetMessage()) fmt.Fprint(os.Stdout, recv.GetMessage())
} }
}
if !req.Foreground {
util.Print(os.Stdout, "Now you can access resources in the kubernetes cluster, enjoy it :)") util.Print(os.Stdout, "Now you can access resources in the kubernetes cluster, enjoy it :)")
} }
return nil return nil
@@ -112,7 +113,8 @@ func CmdConnect(f cmdutil.Factory) *cobra.Command {
cmd.Flags().BoolVar(&transferImage, "transfer-image", false, "transfer image to remote registry, it will transfer image "+config.OriginImage+" to flags `--image` special image, default: "+config.Image) cmd.Flags().BoolVar(&transferImage, "transfer-image", false, "transfer image to remote registry, it will transfer image "+config.OriginImage+" to flags `--image` special image, default: "+config.Image)
cmd.Flags().BoolVar(&connect.UseLocalDNS, "use-localdns", false, "if use-lcoaldns is true, kubevpn will start coredns listen at 53 to forward your dns queries. only support on linux now") cmd.Flags().BoolVar(&connect.UseLocalDNS, "use-localdns", false, "if use-lcoaldns is true, kubevpn will start coredns listen at 53 to forward your dns queries. only support on linux now")
cmd.Flags().StringVar((*string)(&connect.Engine), "engine", string(config.EngineRaw), fmt.Sprintf(`transport engine ("%s"|"%s") %s: use gvisor and raw both (both performance and stable), %s: use raw mode (best stable)`, config.EngineMix, config.EngineRaw, config.EngineMix, config.EngineRaw)) cmd.Flags().StringVar((*string)(&connect.Engine), "engine", string(config.EngineRaw), fmt.Sprintf(`transport engine ("%s"|"%s") %s: use gvisor and raw both (both performance and stable), %s: use raw mode (best stable)`, config.EngineMix, config.EngineRaw, config.EngineMix, config.EngineRaw))
cmd.Flags().BoolVar(&foreground, "foreground", false, "connect to multiple cluster, you needs to special this options") cmd.Flags().BoolVar(&foreground, "foreground", false, "Hang up")
cmd.Flags().BoolVar(&lite, "lite", false, "connect to multiple cluster in lite mode, you needs to special this options")
addSshFlags(cmd, sshConf) addSshFlags(cmd, sshConf)
return cmd return cmd

View File

@@ -50,7 +50,6 @@ func NewKubeVPNCommand() *cobra.Command {
Message: "Develop commands:", Message: "Develop commands:",
Commands: []*cobra.Command{ Commands: []*cobra.Command{
CmdConnect(factory), CmdConnect(factory),
CmdConnectFork(factory),
CmdDisconnect(factory), CmdDisconnect(factory),
CmdProxy(factory), CmdProxy(factory),
CmdLeave(factory), CmdLeave(factory),

View File

@@ -26,7 +26,7 @@ func CmdStatus(f cmdutil.Factory) *cobra.Command {
return daemon.StartupDaemon(cmd.Context()) return daemon.StartupDaemon(cmd.Context())
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
client, err := daemon.GetClient(true).Status( client, err := daemon.GetClient(false).Status(
cmd.Context(), cmd.Context(),
&rpc.StatusRequest{}, &rpc.StatusRequest{},
) )

View File

@@ -91,6 +91,8 @@ const (
// transport mode // transport mode
ConfigKubeVPNTransportEngine = "transport-engine" ConfigKubeVPNTransportEngine = "transport-engine"
// hosts entry key word
HostsKeyWord = "# Add by KubeVPN"
) )
var ( var (

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"io" "io"
"k8s.io/utils/pointer"
defaultlog "log" defaultlog "log"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@@ -86,6 +87,7 @@ func (svr *Server) ConnectFork(req *rpc.ConnectRequest, resp rpc.Daemon_ConnectF
return err return err
} }
svr.secondaryConnect = append(svr.secondaryConnect, connect) svr.secondaryConnect = append(svr.secondaryConnect, connect)
return nil return nil
} }
@@ -147,7 +149,7 @@ func (svr *Server) redirectConnectForkToSudoDaemon(req *rpc.ConnectRequest, resp
return err return err
} }
connResp, err := cli.Connect(ctx, req) connResp, err := cli.ConnectFork(ctx, req)
if err != nil { if err != nil {
return err return err
} }
@@ -165,6 +167,36 @@ func (svr *Server) redirectConnectForkToSudoDaemon(req *rpc.ConnectRequest, resp
} }
svr.secondaryConnect = append(svr.secondaryConnect, connect) svr.secondaryConnect = append(svr.secondaryConnect, connect)
if req.Foreground {
<-resp.Context().Done()
for i := 0; i < len(svr.secondaryConnect); i++ {
if svr.secondaryConnect[i] == connect {
cli := svr.GetClient(false)
if cli == nil {
return fmt.Errorf("sudo daemon not start")
}
disconnect, err := cli.Disconnect(context.Background(), &rpc.DisconnectRequest{
ID: pointer.Int32(int32(i)),
})
if err != nil {
log.Errorf("disconnect error: %v", err)
return err
}
for {
recv, err := disconnect.Recv()
if err == io.EOF {
break
} else if err != nil {
return err
}
log.Info(recv.Message)
}
break
}
}
}
return nil return nil
} }

View File

@@ -181,6 +181,34 @@ func (svr *Server) redirectToSudoDaemon(req *rpc.ConnectRequest, resp rpc.Daemon
svr.t = time.Now() svr.t = time.Now()
svr.connect = connect svr.connect = connect
// hangup
if req.Foreground {
<-resp.Context().Done()
client := svr.GetClient(false)
if client == nil {
return fmt.Errorf("daemon not start")
}
disconnect, err := client.Disconnect(context.Background(), &rpc.DisconnectRequest{
ID: pointer.Int32(int32(0)),
})
if err != nil {
log.Errorf("disconnect error: %v", err)
return err
}
for {
recv, err := disconnect.Recv()
if err == io.EOF {
break
} else if err != nil {
log.Error(err)
return err
}
log.Info(recv.Message)
}
}
return nil return nil
} }

View File

@@ -8,6 +8,7 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/wencaiwulue/kubevpn/pkg/daemon/rpc" "github.com/wencaiwulue/kubevpn/pkg/daemon/rpc"
"github.com/wencaiwulue/kubevpn/pkg/dns"
) )
func (svr *Server) Disconnect(req *rpc.DisconnectRequest, resp rpc.Daemon_DisconnectServer) error { func (svr *Server) Disconnect(req *rpc.DisconnectRequest, resp rpc.Daemon_DisconnectServer) error {
@@ -77,6 +78,10 @@ func (svr *Server) Disconnect(req *rpc.DisconnectRequest, resp rpc.Daemon_Discon
log.Errorf("index %d out of range", req.GetID()) log.Errorf("index %d out of range", req.GetID())
} }
} }
if svr.connect == nil && len(svr.secondaryConnect) == 0 {
dns.CleanupHosts()
}
return nil return nil
} }

View File

@@ -6,6 +6,7 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/wencaiwulue/kubevpn/pkg/daemon/rpc" "github.com/wencaiwulue/kubevpn/pkg/daemon/rpc"
"github.com/wencaiwulue/kubevpn/pkg/dns"
) )
func (svr *Server) Quit(req *rpc.QuitRequest, resp rpc.Daemon_QuitServer) error { func (svr *Server) Quit(req *rpc.QuitRequest, resp rpc.Daemon_QuitServer) error {
@@ -33,6 +34,9 @@ func (svr *Server) Quit(req *rpc.QuitRequest, resp rpc.Daemon_QuitServer) error
log.Info("quit: cleanup connection") log.Info("quit: cleanup connection")
options.Cleanup() options.Cleanup()
} }
dns.CleanupHosts()
return nil return nil
} }

View File

@@ -28,7 +28,7 @@ import (
"github.com/wencaiwulue/kubevpn/pkg/config" "github.com/wencaiwulue/kubevpn/pkg/config"
"github.com/wencaiwulue/kubevpn/pkg/cp" "github.com/wencaiwulue/kubevpn/pkg/cp"
"github.com/wencaiwulue/kubevpn/pkg/dns" util2 "github.com/wencaiwulue/kubevpn/pkg/util"
) )
type RunConfig struct { type RunConfig struct {
@@ -186,7 +186,7 @@ func GetDNS(ctx context.Context, f util.Factory, ns, pod string) (*miekgdns.Clie
return nil, err return nil, err
} }
clientConfig, err := dns.GetDNSServiceIPFromPod(clientSet, client, config, pod, ns) clientConfig, err := util2.GetDNSServiceIPFromPod(clientSet, client, config, pod, ns)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -9,17 +9,17 @@ type CoreFile struct {
Content []byte Content []byte
} }
// Gets the Caddyfile contents // Body Gets the Caddyfile contents
func (c *CoreFile) Body() []byte { func (c *CoreFile) Body() []byte {
return c.Content return c.Content
} }
// Gets the path to the origin file // Path Gets the path to the origin file
func (c *CoreFile) Path() string { func (c *CoreFile) Path() string {
return "CoreFile" return "CoreFile"
} }
// The type of server this input is intended for // ServerType The type of server this input is intended for
func (c *CoreFile) ServerType() string { func (c *CoreFile) ServerType() string {
return "dns" return "dns"
} }

View File

@@ -12,87 +12,41 @@ import (
"time" "time"
miekgdns "github.com/miekg/dns" miekgdns "github.com/miekg/dns"
"github.com/pkg/errors"
v12 "k8s.io/api/core/v1" v12 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
utilnet "k8s.io/apimachinery/pkg/util/net" utilnet "k8s.io/apimachinery/pkg/util/net"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/watch" "k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/kubernetes"
v13 "k8s.io/client-go/kubernetes/typed/core/v1" v13 "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/rest"
"k8s.io/client-go/util/flowcontrol" "k8s.io/client-go/util/flowcontrol"
"github.com/wencaiwulue/kubevpn/pkg/util" "github.com/wencaiwulue/kubevpn/pkg/config"
) )
func GetDNSServiceIPFromPod(clientset *kubernetes.Clientset, restclient *rest.RESTClient, config *rest.Config, podName, namespace string) (*miekgdns.ClientConfig, error) { type Config struct {
resolvConfStr, err := util.Shell(clientset, restclient, config, podName, "", namespace, []string{"cat", "/etc/resolv.conf"}) Config *miekgdns.ClientConfig
if err != nil { Ns []string
return nil, err UseLocalDNS bool
} TunName string
resolvConf, err := miekgdns.ClientConfigFromReader(bytes.NewBufferString(resolvConfStr))
if err != nil { Hosts []Entry
return nil, err
}
if ips, err := GetDNSIPFromDnsPod(clientset); err == nil && len(ips) != 0 {
resolvConf.Servers = ips
} }
// linux nameserver only support amount is 3, so if namespace too much, just use two, left one to system func (c *Config) AddServiceNameToHosts(ctx context.Context, serviceInterface v13.ServiceInterface, hosts ...Entry) {
if len(resolvConf.Servers) > 2 {
resolvConf.Servers = resolvConf.Servers[:2]
}
return resolvConf, nil
}
func GetDNSIPFromDnsPod(clientset *kubernetes.Clientset) (ips []string, err error) {
var serviceList *v12.ServiceList
serviceList, err = clientset.CoreV1().Services(v1.NamespaceSystem).List(context.Background(), v1.ListOptions{
LabelSelector: fields.OneTermEqualSelector("k8s-app", "kube-dns").String(),
})
if err != nil {
return
}
for _, item := range serviceList.Items {
if len(item.Spec.ClusterIP) != 0 {
ips = append(ips, item.Spec.ClusterIP)
}
}
var podList *v12.PodList
podList, err = clientset.CoreV1().Pods(v1.NamespaceSystem).List(context.Background(), v1.ListOptions{
LabelSelector: fields.OneTermEqualSelector("k8s-app", "kube-dns").String(),
})
if err == nil {
for _, pod := range podList.Items {
if pod.Status.Phase == v12.PodRunning && pod.DeletionTimestamp == nil {
ips = append(ips, pod.Status.PodIP)
}
}
}
if len(ips) == 0 {
err = errors.New("can not found any dns service ip")
return
}
err = nil
return
}
func AddServiceNameToHosts(ctx context.Context, serviceInterface v13.ServiceInterface, hosts ...Entry) {
rateLimiter := flowcontrol.NewTokenBucketRateLimiter(0.2, 1) rateLimiter := flowcontrol.NewTokenBucketRateLimiter(0.2, 1)
defer rateLimiter.Stop() defer rateLimiter.Stop()
var last string var last string
serviceList, err := serviceInterface.List(ctx, v1.ListOptions{}) serviceList, err := serviceInterface.List(ctx, v1.ListOptions{})
if err == nil && len(serviceList.Items) != 0 { if err == nil && len(serviceList.Items) != 0 {
entry := generateHostsEntry(serviceList.Items, hosts) entry := c.generateHostsEntry(serviceList.Items, hosts)
if err = updateHosts(entry); err == nil { if entry != "" {
if err = c.updateHosts(entry); err == nil {
last = entry last = entry
} }
} }
}
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
@@ -102,6 +56,7 @@ func AddServiceNameToHosts(ctx context.Context, serviceInterface v13.ServiceInte
w, err := serviceInterface.Watch(ctx, v1.ListOptions{ w, err := serviceInterface.Watch(ctx, v1.ListOptions{
Watch: true, ResourceVersion: serviceList.ResourceVersion, Watch: true, ResourceVersion: serviceList.ResourceVersion,
}) })
if err != nil { if err != nil {
if utilnet.IsConnectionRefused(err) || apierrors.IsTooManyRequests(err) { if utilnet.IsConnectionRefused(err) || apierrors.IsTooManyRequests(err) {
time.Sleep(time.Second * 5) time.Sleep(time.Second * 5)
@@ -111,11 +66,11 @@ func AddServiceNameToHosts(ctx context.Context, serviceInterface v13.ServiceInte
defer w.Stop() defer w.Stop()
for { for {
select { select {
case c, ok := <-w.ResultChan(): case event, ok := <-w.ResultChan():
if !ok { if !ok {
return return
} }
if watch.Error == c.Type || watch.Bookmark == c.Type { if watch.Error == event.Type || watch.Bookmark == event.Type {
continue continue
} }
if !rateLimiter.TryAccept() { if !rateLimiter.TryAccept() {
@@ -125,11 +80,14 @@ func AddServiceNameToHosts(ctx context.Context, serviceInterface v13.ServiceInte
if err != nil { if err != nil {
return return
} }
entry := generateHostsEntry(list.Items, hosts) entry := c.generateHostsEntry(list.Items, hosts)
if entry == "" {
continue
}
if entry == last { if entry == last {
continue continue
} }
if err = updateHosts(entry); err != nil { if err = c.updateHosts(entry); err != nil {
return return
} }
last = entry last = entry
@@ -140,46 +98,44 @@ func AddServiceNameToHosts(ctx context.Context, serviceInterface v13.ServiceInte
} }
} }
func updateHosts(str string) error { func (c *Config) updateHosts(str string) error {
if len(str) == 0 {
return nil
}
path := GetHostFile() path := GetHostFile()
file, err := os.ReadFile(path) file, err := os.ReadFile(path)
if err != nil { if err != nil {
return err return err
} }
split := strings.Split(string(file), "\n") lines := strings.Split(string(file), "\n")
//for i := 0; i < len(split); i++ { for i := 0; i < len(lines); i++ {
// if strings.Contains(split[i], "KubeVPN") { line := lines[i]
// split = append(split[:i], split[i+1:]...) if strings.Contains(line, config.HostsKeyWord) {
// i-- for _, host := range c.Hosts {
// } if strings.Contains(line, host.Domain) {
//} lines = append(lines[:i], lines[i+1:]...)
var sb strings.Builder i--
}
}
}
}
if len(lines) == 0 {
return fmt.Errorf("empty hosts file")
}
sb.WriteString(strings.Join(split, "\n")) var sb strings.Builder
sb.WriteString(strings.Join(lines, "\n"))
if str != "" { if str != "" {
sb.WriteString("\n") sb.WriteString("\n")
sb.WriteString(str) sb.WriteString(str)
} }
s := sb.String() s := sb.String()
// remove last empty line // remove last empty line
strList := strings.Split(s, "\n") s = strings.TrimRight(s, "\n")
for {
if len(strList) > 0 { if strings.TrimSpace(s) == "" {
if strList[len(strList)-1] == "" { return fmt.Errorf("empty content after update")
strList = strList[:len(strList)-1]
continue
}
}
break
} }
return os.WriteFile(path, []byte(strings.Join(strList, "\n")), 0644) return os.WriteFile(path, []byte(s), 0644)
} }
type Entry struct { type Entry struct {
@@ -187,10 +143,11 @@ type Entry struct {
Domain string Domain string
} }
func generateHostsEntry(list []v12.Service, hosts []Entry) string { func (c *Config) generateHostsEntry(list []v12.Service, hosts []Entry) string {
const ServiceKubernetes = "kubernetes" const ServiceKubernetes = "kubernetes"
var entryList []Entry var entryList []Entry
// get all service ip
for _, item := range list { for _, item := range list {
if strings.EqualFold(item.Name, ServiceKubernetes) { if strings.EqualFold(item.Name, ServiceKubernetes) {
continue continue
@@ -214,7 +171,7 @@ func generateHostsEntry(list []v12.Service, hosts []Entry) string {
}) })
entryList = append(entryList, hosts...) entryList = append(entryList, hosts...)
// 判断是否是通的,或者直接用查询是否有同样条目的记录 // if dns already works well, not needs to add it to hosts file
for i := 0; i < len(entryList); i++ { for i := 0; i < len(entryList); i++ {
e := entryList[i] e := entryList[i]
host, err := net.LookupHost(e.Domain) host, err := net.LookupHost(e.Domain)
@@ -224,11 +181,53 @@ func generateHostsEntry(list []v12.Service, hosts []Entry) string {
} }
} }
// if hosts file already contains item, not needs to add it to hosts file
file, err := os.ReadFile(GetHostFile())
if err != nil {
return ""
}
lines := strings.Split(string(file), "\n")
for i := 0; i < len(lines); i++ {
line := lines[i]
for j := 0; j < len(entryList); j++ {
entry := entryList[j]
if strings.Contains(line, entry.Domain) && strings.Contains(line, entry.IP) {
entryList = append(entryList[:j], entryList[j+1:]...)
j--
}
}
}
c.Hosts = append(c.Hosts, entryList...)
var sb = new(bytes.Buffer) var sb = new(bytes.Buffer)
w := tabwriter.NewWriter(sb, 1, 1, 1, ' ', 0) w := tabwriter.NewWriter(sb, 1, 1, 1, ' ', 0)
for _, e := range entryList { for _, e := range entryList {
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", e.IP, e.Domain, "", "# Add by KubeVPN") _, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", e.IP, e.Domain, "", config.HostsKeyWord)
} }
_ = w.Flush() _ = w.Flush()
return sb.String() return sb.String()
} }
func CleanupHosts() error {
path := GetHostFile()
file, err := os.ReadFile(path)
if err != nil {
return err
}
lines := strings.Split(string(file), "\n")
for i := 0; i < len(lines); i++ {
line := lines[i]
if strings.Contains(line, config.HostsKeyWord) {
lines = append(lines[:i], lines[i+1:]...)
i--
}
}
if len(lines) == 0 {
return fmt.Errorf("empty hosts file")
}
var sb strings.Builder
sb.WriteString(strings.Join(lines, "\n"))
return os.WriteFile(path, []byte(sb.String()), 0644)
}

View File

@@ -1,5 +1,4 @@
//go:build linux //go:build linux
// +build linux
package dns package dns
@@ -21,7 +20,11 @@ import (
) )
// systemd-resolve --status, systemd-resolve --flush-caches // systemd-resolve --status, systemd-resolve --flush-caches
func SetupDNS(clientConfig *miekgdns.ClientConfig, _ []string, useLocalDNS bool, tunName string) error { func (c *Config) SetupDNS() error {
clientConfig := c.Config
useLocalDNS := c.UseLocalDNS
tunName := c.TunName
existNameservers := make([]string, 0) existNameservers := make([]string, 0)
existSearches := make([]string, 0) existSearches := make([]string, 0)
filename := filepath.Join("/", "etc", "resolv.conf") filename := filepath.Join("/", "etc", "resolv.conf")
@@ -39,7 +42,7 @@ func SetupDNS(clientConfig *miekgdns.ClientConfig, _ []string, useLocalDNS bool,
} }
if useLocalDNS { if useLocalDNS {
if err := SetupLocalDNS(clientConfig, existNameservers); err != nil { if err = SetupLocalDNS(clientConfig, existNameservers); err != nil {
return err return err
} }
clientConfig.Servers[0] = "127.0.0.1" clientConfig.Servers[0] = "127.0.0.1"
@@ -103,8 +106,8 @@ func SetupLocalDNS(clientConfig *miekgdns.ClientConfig, existNameservers []strin
return nil return nil
} }
func CancelDNS(tunName string) { func (c *Config) CancelDNS() {
updateHosts("") c.updateHosts("")
filename := filepath.Join("/", "etc", "resolv.conf") filename := filepath.Join("/", "etc", "resolv.conf")
_ = os.Rename(getBackupFilename(filename), filename) _ = os.Rename(getBackupFilename(filename), filename)

View File

@@ -1,5 +1,4 @@
//go:build darwin //go:build darwin
// +build darwin
package dns package dns
@@ -31,15 +30,18 @@ var resolv = "/etc/resolv.conf"
// service.namespace.svc:port // service.namespace.svc:port
// service.namespace.svc.cluster:port // service.namespace.svc.cluster:port
// service.namespace.svc.cluster.local:port // service.namespace.svc.cluster.local:port
func SetupDNS(config *miekgdns.ClientConfig, ns []string, _ bool, tunName string) error { func (c *Config) SetupDNS() error {
usingResolver(config, ns, tunName) c.usingResolver()
_ = exec.Command("killall", "mDNSResponderHelper").Run() _ = exec.Command("killall", "mDNSResponderHelper").Run()
_ = exec.Command("killall", "-HUP", "mDNSResponder").Run() _ = exec.Command("killall", "-HUP", "mDNSResponder").Run()
_ = exec.Command("dscacheutil", "-flushcache").Run() _ = exec.Command("dscacheutil", "-flushcache").Run()
return nil return nil
} }
func usingResolver(clientConfig *miekgdns.ClientConfig, ns []string, tunName string) { func (c *Config) usingResolver() {
var clientConfig = c.Config
var ns = c.Ns
var err error var err error
_ = os.RemoveAll(filepath.Join("/", "etc", "resolver")) _ = os.RemoveAll(filepath.Join("/", "etc", "resolver"))
if err = os.MkdirAll(filepath.Join("/", "etc", "resolver"), fs.ModePerm); err != nil { if err = os.MkdirAll(filepath.Join("/", "etc", "resolver"), fs.ModePerm); err != nil {
@@ -80,7 +82,7 @@ func usingResolver(clientConfig *miekgdns.ClientConfig, ns []string, tunName str
} }
} }
func usingNetworkSetup(ip string, namespace string) { func (c *Config) usingNetworkSetup(ip string, namespace string) {
networkSetup(ip, namespace) networkSetup(ip, namespace)
var ctx context.Context var ctx context.Context
ctx, cancel = context.WithCancel(context.Background()) ctx, cancel = context.WithCancel(context.Background())
@@ -153,13 +155,13 @@ func toString(config miekgdns.ClientConfig) string {
return builder.String() return builder.String()
} }
func CancelDNS(tunName string) { func (c *Config) CancelDNS() {
if cancel != nil { if cancel != nil {
cancel() cancel()
} }
_ = os.RemoveAll(filepath.Join("/", "etc", "resolver")) _ = os.RemoveAll(filepath.Join("/", "etc", "resolver"))
//networkCancel() //networkCancel()
//updateHosts("") c.updateHosts("")
} }
/* /*

View File

@@ -1,5 +1,4 @@
//go:build windows //go:build windows
// +build windows
package dns package dns
@@ -9,18 +8,23 @@ import (
"net/netip" "net/netip"
"os/exec" "os/exec"
miekgdns "github.com/miekg/dns"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
) )
func SetupDNS(clientConfig *miekgdns.ClientConfig, _ []string, _ bool, tunName string) error { func (c *Config) SetupDNS() error {
clientConfig := c.Config
tunName := c.TunName
tun, err := net.InterfaceByName(tunName) tun, err := net.InterfaceByName(tunName)
if err != nil { if err != nil {
return err return err
} }
luid := winipcfg.LUIDFromIndex(tun.Index) luid, err := winipcfg.LUIDFromIndex(uint32(tun.Index))
if err != nil {
return err
}
var servers []netip.Addr var servers []netip.Addr
for _, s := range clientConfig.Servers { for _, s := range clientConfig.Servers {
var addr netip.Addr var addr netip.Addr
@@ -41,13 +45,16 @@ func SetupDNS(clientConfig *miekgdns.ClientConfig, _ []string, _ bool, tunName s
return nil return nil
} }
func CancelDNS(tunName string) { func (c *Config) CancelDNS() {
updateHosts("") c.updateHosts("")
tun, err := net.InterfaceByName(tunName) tun, err := net.InterfaceByName(c.TunName)
if err != nil {
return
}
luid, err := winipcfg.LUIDFromIndex(uint32(tun.Index))
if err != nil { if err != nil {
return return
} }
luid := winipcfg.LUIDFromIndex(tun.Index)
_ = luid.FlushDNS(windows.AF_INET) _ = luid.FlushDNS(windows.AF_INET)
_ = luid.FlushRoutes(windows.AF_INET) _ = luid.FlushRoutes(windows.AF_INET)
} }

View File

@@ -21,7 +21,6 @@ import (
"k8s.io/utils/pointer" "k8s.io/utils/pointer"
"github.com/wencaiwulue/kubevpn/pkg/config" "github.com/wencaiwulue/kubevpn/pkg/config"
"github.com/wencaiwulue/kubevpn/pkg/dns"
"github.com/wencaiwulue/kubevpn/pkg/util" "github.com/wencaiwulue/kubevpn/pkg/util"
) )
@@ -79,11 +78,10 @@ func (c *ConnectOptions) Cleanup() {
c.cancel() c.cancel()
} }
c.RollbackFuncList = c.RollbackFuncList[:] c.RollbackFuncList = c.RollbackFuncList[:]
name, err := c.GetTunDeviceName() if c.dnsConfig != nil {
if err == nil { log.Infof("clean up dns")
log.Errorf("get tun device error: %v", err) c.dnsConfig.CancelDNS()
} }
dns.CancelDNS(name)
log.Info("clean up successfully") log.Info("clean up successfully")
util.CleanExtensionLib() util.CleanExtensionLib()
} }

View File

@@ -92,6 +92,7 @@ type ConnectOptions struct {
localTunIPv4 *net.IPNet localTunIPv4 *net.IPNet
localTunIPv6 *net.IPNet localTunIPv6 *net.IPNet
RollbackFuncList []func() RollbackFuncList []func()
dnsConfig *dns.Config
apiServerIPs []net.IP apiServerIPs []net.IP
extraHost []dns.Entry extraHost []dns.Entry
@@ -620,7 +621,7 @@ func (c *ConnectOptions) setupDNS(ctx context.Context) error {
log.Errorf("get running pod list failed, err: %v", err) log.Errorf("get running pod list failed, err: %v", err)
return err return err
} }
relovConf, err := dns.GetDNSServiceIPFromPod(c.clientset, c.restclient, c.config, pod[0].GetName(), c.Namespace) relovConf, err := util.GetDNSServiceIPFromPod(c.clientset, c.restclient, c.config, pod[0].GetName(), c.Namespace)
if err != nil { if err != nil {
log.Errorln(err) log.Errorln(err)
return err return err
@@ -645,11 +646,18 @@ func (c *ConnectOptions) setupDNS(ctx context.Context) error {
if err != nil { if err != nil {
return err return err
} }
if err = dns.SetupDNS(relovConf, ns.UnsortedList(), c.UseLocalDNS, tunName); err != nil { c.dnsConfig = &dns.Config{
Config: relovConf,
Ns: ns.UnsortedList(),
UseLocalDNS: c.UseLocalDNS,
TunName: tunName,
Hosts: c.extraHost,
}
if err = c.dnsConfig.SetupDNS(); err != nil {
return err return err
} }
// dump service in current namespace for support DNS resolve service:port // dump service in current namespace for support DNS resolve service:port
go dns.AddServiceNameToHosts(ctx, c.clientset.CoreV1().Services(c.Namespace), c.extraHost...) go c.dnsConfig.AddServiceNameToHosts(ctx, c.clientset.CoreV1().Services(c.Namespace), c.extraHost...)
return nil return nil
} }
@@ -1077,7 +1085,7 @@ func (c *ConnectOptions) addExtraRoute(ctx context.Context) error {
if len(c.ExtraDomain) == 0 { if len(c.ExtraDomain) == 0 {
return nil return nil
} }
ips, err := dns.GetDNSIPFromDnsPod(c.clientset) ips, err := util.GetDNSIPFromDnsPod(c.clientset)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -91,7 +91,10 @@ func addTunRoutes(tunName string, routes ...types.Route) error {
if err2 != nil { if err2 != nil {
return err2 return err2
} }
ifName := winipcfg.LUIDFromIndex(name.Index) ifName, err := winipcfg.LUIDFromIndex(uint32(name.Index))
if err != nil {
return err
}
for _, route := range routes { for _, route := range routes {
if route.Dst.String() == "" { if route.Dst.String() == "" {
continue continue

67
pkg/util/dns.go Normal file
View File

@@ -0,0 +1,67 @@
package util
import (
"bytes"
"context"
"github.com/miekg/dns"
"github.com/pkg/errors"
"k8s.io/api/core/v1"
v12 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)
func GetDNSServiceIPFromPod(clientset *kubernetes.Clientset, restclient *rest.RESTClient, config *rest.Config, podName, namespace string) (*dns.ClientConfig, error) {
resolvConfStr, err := Shell(clientset, restclient, config, podName, "", namespace, []string{"cat", "/etc/resolv.conf"})
if err != nil {
return nil, err
}
resolvConf, err := dns.ClientConfigFromReader(bytes.NewBufferString(resolvConfStr))
if err != nil {
return nil, err
}
if ips, err := GetDNSIPFromDnsPod(clientset); err == nil && len(ips) != 0 {
resolvConf.Servers = ips
}
// linux nameserver only support amount is 3, so if namespace too much, just use two, left one to system
if len(resolvConf.Servers) > 2 {
resolvConf.Servers = resolvConf.Servers[:2]
}
return resolvConf, nil
}
func GetDNSIPFromDnsPod(clientset *kubernetes.Clientset) (ips []string, err error) {
var serviceList *v1.ServiceList
serviceList, err = clientset.CoreV1().Services(v12.NamespaceSystem).List(context.Background(), v12.ListOptions{
LabelSelector: fields.OneTermEqualSelector("k8s-app", "kube-dns").String(),
})
if err != nil {
return
}
for _, item := range serviceList.Items {
if len(item.Spec.ClusterIP) != 0 {
ips = append(ips, item.Spec.ClusterIP)
}
}
var podList *v1.PodList
podList, err = clientset.CoreV1().Pods(v12.NamespaceSystem).List(context.Background(), v12.ListOptions{
LabelSelector: fields.OneTermEqualSelector("k8s-app", "kube-dns").String(),
})
if err == nil {
for _, pod := range podList.Items {
if pod.Status.Phase == v1.PodRunning && pod.DeletionTimestamp == nil {
ips = append(ips, pod.Status.PodIP)
}
}
}
if len(ips) == 0 {
err = errors.New("can not found any dns service ip")
return
}
err = nil
return
}