diff --git a/go.mod b/go.mod index 5e8c5ae11..bfd88baa9 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/emirpasic/gods v1.18.1 github.com/fatih/color v1.16.0 github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa + github.com/gaissmai/extnetip v0.4.0 github.com/go-acme/lego/v4 v4.15.0 github.com/go-openapi/errors v0.21.0 github.com/go-openapi/loads v0.21.5 diff --git a/go.sum b/go.sum index 2432d74b9..3e5fb0829 100644 --- a/go.sum +++ b/go.sum @@ -192,11 +192,11 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa h1:RDBNVkRviHZtvDvId8XSGPu3rmpmSe+wKRcEWNgsfWU= github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= +github.com/gaissmai/extnetip v0.4.0 h1:9pNd/Z6QSlkda35bug/IYuPYaPMTYRuqcxPce5Z9TTQ= +github.com/gaissmai/extnetip v0.4.0/go.mod h1:M3NWlyFKaVosQXWXKKeIPK+5VM4U85DahdIqNYX4TK4= github.com/getkin/kin-openapi v0.13.0/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s+pcEVXFuAjw= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= -github.com/go-acme/lego/v4 v4.14.2 h1:/D/jqRgLi8Cbk33sLGtu2pX2jEg3bGJWHyV8kFuUHGM= -github.com/go-acme/lego/v4 v4.14.2/go.mod h1:kBXxbeTg0x9AgaOYjPSwIeJy3Y33zTz+tMD16O4MO6c= github.com/go-acme/lego/v4 v4.15.0 h1:A7MHEU3b+TDFqhC/HmzMJnzPbyeaYvMZQBbqgvbThhU= github.com/go-acme/lego/v4 v4.15.0/go.mod h1:eeGhjW4zWT7Ccqa3sY7ayEqFLCAICx+mXgkMHKIkLxg= github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= @@ -236,8 +236,6 @@ github.com/go-openapi/strfmt v0.22.0 h1:Ew9PnEYc246TwrEspvBdDHS4BVKXy/AOVsfqGDgA github.com/go-openapi/strfmt v0.22.0/go.mod h1:HzJ9kokGIju3/K6ap8jL+OlGAbjpSv27135Yr9OivU4= github.com/go-openapi/swag v0.22.9 h1:XX2DssF+mQKM2DHsbgZK74y/zj4mo9I99+89xUmuZCE= github.com/go-openapi/swag v0.22.9/go.mod h1:3/OXnFfnMAwBD099SwYRk7GD3xOrr1iL7d/XNLXVVwE= -github.com/go-openapi/validate v0.22.6 h1:+NhuwcEYpWdO5Nm4bmvhGLW0rt1Fcc532Mu3wpypXfo= -github.com/go-openapi/validate v0.22.6/go.mod h1:eaddXSqKeTg5XpSmj1dYyFTK/95n/XHwcOY+BMxKMyM= github.com/go-openapi/validate v0.23.0 h1:2l7PJLzCis4YUGEoW6eoQw3WhyM65WSIcjX6SQnlfDw= github.com/go-openapi/validate v0.23.0/go.mod h1:EeiAZ5bmpSIOJV1WLfyYF9qp/B1ZgSaEpHTJHtN5cbE= github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8= @@ -404,8 +402,6 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/influxdata/influxdb-client-go/v2 v2.2.2/go.mod h1:fa/d1lAdUHxuc1jedx30ZfNG573oQTQmUni3N6pcW+0= github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= -github.com/jedib0t/go-pretty/v6 v6.5.3 h1:GIXn6Er/anHTkVUoufs7ptEvxdD6KIhR7Axa2wYCPF0= -github.com/jedib0t/go-pretty/v6 v6.5.3/go.mod h1:5LQIxa52oJ/DlDSLv0HEkWOFMDGoWkJb9ss5KqPpJBg= github.com/jedib0t/go-pretty/v6 v6.5.4 h1:gOGo0613MoqUcf0xCj+h/V3sHDaZasfv152G6/5l91s= github.com/jedib0t/go-pretty/v6 v6.5.4/go.mod h1:5LQIxa52oJ/DlDSLv0HEkWOFMDGoWkJb9ss5KqPpJBg= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= @@ -518,8 +514,6 @@ github.com/michaelquigley/pfxlog v0.6.10 h1:IbC/H3MmSDcPlQHF1UZPQU13Dkrs0+ycWRyQ github.com/michaelquigley/pfxlog v0.6.10/go.mod h1:gEiNTfKEX6cJHSwRpOuqBpc8oYrlhMiDK/xMk/gV7D0= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= -github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU= @@ -753,9 +747,9 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.1 h1:4VhoImhV/Bm0ToFkXFi8hXNXwpDRZ/ynw3amt82mzq0= +github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0= github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -872,8 +866,6 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -975,8 +967,6 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1182,8 +1172,6 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= -golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/router/xgress_edge_tunnel/tunneler.go b/router/xgress_edge_tunnel/tunneler.go index cf1161141..879e26da1 100644 --- a/router/xgress_edge_tunnel/tunneler.go +++ b/router/xgress_edge_tunnel/tunneler.go @@ -70,6 +70,17 @@ func (self *tunneler) Start(notifyClose <-chan struct{}) error { log := pfxlog.Logger() log.WithField("mode", self.listenOptions.mode).Info("creating interceptor") + resolver, err := dns.NewResolver(self.listenOptions.resolver) + if err != nil { + pfxlog.Logger().WithError(err).Error("failed to start DNS resolver. using dummy resolver") + resolver = dns.NewDummyResolver() + } + + if err = intercept.SetDnsInterceptIpRange(self.listenOptions.dnsSvcIpRange); err != nil { + pfxlog.Logger().Errorf("invalid dns service IP range %s: %v", self.listenOptions.dnsSvcIpRange, err) + return err + } + if strings.HasPrefix(self.listenOptions.mode, "tproxy") { tproxyConfig := tproxy.Config{ LanIf: self.listenOptions.lanIf, @@ -96,16 +107,6 @@ func (self *tunneler) Start(notifyClose <-chan struct{}) error { return errors.Errorf("unsupported tunnel mode '%v'", self.listenOptions.mode) } - resolver, err := dns.NewResolver(self.listenOptions.resolver) - if err != nil { - pfxlog.Logger().WithError(err).Error("failed to start DNS resolver") - } - - if err = intercept.SetDnsInterceptIpRange(self.listenOptions.dnsSvcIpRange); err != nil { - pfxlog.Logger().Errorf("invalid dns service IP range %s: %v", self.listenOptions.dnsSvcIpRange, err) - return err - } - self.servicePoller.serviceListener = intercept.NewServiceListener(self.interceptor, resolver) self.servicePoller.serviceListener.HandleProviderReady(self.fabricProvider) diff --git a/tunnel/dns/dummy.go b/tunnel/dns/dummy.go new file mode 100644 index 000000000..e109ab1ae --- /dev/null +++ b/tunnel/dns/dummy.go @@ -0,0 +1,39 @@ +package dns + +import ( + "github.com/michaelquigley/pfxlog" + "net" +) + +type dummy struct{} + +func (d dummy) AddHostname(_ string, _ net.IP) error { + pfxlog.Logger().Warnf("dummy resolver does not store hostname/ip mappings") + return nil +} + +func (d dummy) AddDomain(_ string, _ func(string) (net.IP, error)) error { + pfxlog.Logger().Warnf("dummy resolver does not store hostname/ip mappings") + return nil +} + +func (d dummy) Lookup(_ net.IP) (string, error) { + pfxlog.Logger().Warnf("dummy resolver does not store hostname/ip mappings") + return "", nil +} + +func (d dummy) RemoveHostname(_ string) net.IP { + return nil +} + +func (d dummy) RemoveDomain(_ string) { +} + +func (d dummy) Cleanup() error { + return nil +} + +func NewDummyResolver() Resolver { + pfxlog.Logger().Warnf("dummy resolver does not store hostname/ip mappings") + return &dummy{} +} diff --git a/tunnel/dns/file.go b/tunnel/dns/file.go index 04584699e..6e09cc214 100644 --- a/tunnel/dns/file.go +++ b/tunnel/dns/file.go @@ -54,6 +54,8 @@ func (h *hostFile) AddDomain(name string, _ func(string) (net.IP, error)) error return fmt.Errorf("cannot add wildcard domain[%s] to hostfile resolver", name) } +func (h *hostFile) RemoveDomain(string) {} + func (h *hostFile) Lookup(_ net.IP) (string, error) { return "", fmt.Errorf("not implemented") } @@ -81,7 +83,7 @@ func (h *hostFile) AddHostname(hostname string, ip net.IP) error { return nil } -func (h *hostFile) RemoveHostname(_ string) error { +func (h *hostFile) RemoveHostname(_ string) net.IP { return nil } diff --git a/tunnel/dns/refcount.go b/tunnel/dns/refcount.go index 7a0cdc1d7..55b67b4b9 100644 --- a/tunnel/dns/refcount.go +++ b/tunnel/dns/refcount.go @@ -25,6 +25,10 @@ func (self *RefCountingResolver) AddDomain(name string, cb func(string) (net.IP, return self.wrapped.AddDomain(name, cb) } +func (self *RefCountingResolver) RemoveDomain(name string) { + self.wrapped.RemoveDomain(name) +} + func (self *RefCountingResolver) AddHostname(s string, ip net.IP) error { err := self.wrapped.AddHostname(s, ip) if err != nil { @@ -38,7 +42,7 @@ func (self *RefCountingResolver) AddHostname(s string, ip net.IP) error { return err } -func (self *RefCountingResolver) RemoveHostname(s string) error { +func (self *RefCountingResolver) RemoveHostname(s string) net.IP { val := self.names.Upsert(s, 1, func(exist bool, valueInMap int, newValue int) int { if exist { return valueInMap - 1 diff --git a/tunnel/dns/resolver.go b/tunnel/dns/resolver.go index 2c1addcd1..d8279b632 100644 --- a/tunnel/dns/resolver.go +++ b/tunnel/dns/resolver.go @@ -22,7 +22,8 @@ type Resolver interface { AddHostname(string, net.IP) error AddDomain(string, func(string) (net.IP, error)) error Lookup(net.IP) (string, error) - RemoveHostname(string) error + RemoveHostname(string) net.IP + RemoveDomain(string) Cleanup() error } diff --git a/tunnel/dns/server.go b/tunnel/dns/server.go index 303d8df87..c6652a77f 100644 --- a/tunnel/dns/server.go +++ b/tunnel/dns/server.go @@ -186,12 +186,13 @@ func (r *resolver) getAddress(name string) (net.IP, error) { de, ok := r.domains[canonical] if ok { + name = name[:len(name)-1] ip, err := de.getIP(name) if err != nil { return nil, err } log.Debugf("assigned %v => %v", name, ip) - _ = r.AddHostname(name[:len(name)-1], ip) // this resolver impl never returns an error + _ = r.AddHostname(name, ip) // this resolver impl never returns an error return ip, err } } @@ -250,6 +251,18 @@ func (r *resolver) AddDomain(name string, ipCB func(string) (net.IP, error)) err return nil } +func (r *resolver) RemoveDomain(name string) { + if name[0] != '*' { + log.Warnf("invalid wildcard domain '%s'", name) + return + } + domainSfx := name[1:] + "." + r.domainsMtx.Lock() + defer r.domainsMtx.Unlock() + log.Infof("removing domain %s from resolver", domainSfx) + delete(r.domains, domainSfx) +} + func (r *resolver) AddHostname(hostname string, ip net.IP) error { r.namesMtx.Lock() defer r.namesMtx.Unlock() @@ -277,18 +290,20 @@ func (r *resolver) Lookup(ip net.IP) (string, error) { return "", errors.New("not found") } -func (r *resolver) RemoveHostname(hostname string) error { +func (r *resolver) RemoveHostname(hostname string) net.IP { r.namesMtx.Lock() defer r.namesMtx.Unlock() key := strings.ToLower(hostname) + "." - if ip, ok := r.names[key]; ok { + var ip net.IP + var ok bool + if ip, ok = r.names[key]; ok { log.Infof("removing %s from resolver", hostname) delete(r.ips, ip.String()) delete(r.names, key) } - return nil + return ip } func (r *resolver) Cleanup() error { diff --git a/tunnel/entities/service.go b/tunnel/entities/service.go index a7a77e918..fa2a5a7a1 100644 --- a/tunnel/entities/service.go +++ b/tunnel/entities/service.go @@ -164,7 +164,7 @@ func makeAllowedAddress(addr string) (allowedAddress, error) { return &domainAddress{domain: strings.ToLower(addr)}, nil } - if _, cidr, err := utils.GetDialIP(addr); err == nil { + if cidr, err := utils.GetCidr(addr); err == nil { return &cidrAddress{cidr: *cidr}, nil } @@ -291,7 +291,7 @@ func (self *HostV1Config) GetAllowedSourceAddressRoutes() ([]*net.IPNet, error) var routes []*net.IPNet for _, addr := range self.AllowedSourceAddresses { // need to get CIDR from address - iputils.getInterceptIp? - _, ipNet, err := utils.GetDialIP(addr) + ipNet, err := utils.GetCidr(addr) if err != nil { return nil, errors.Errorf("failed to parse allowed source address '%s': %v", addr, err) } diff --git a/tunnel/intercept/hosting.go b/tunnel/intercept/hosting.go index 79da2b60e..5d403f68b 100644 --- a/tunnel/intercept/hosting.go +++ b/tunnel/intercept/hosting.go @@ -193,7 +193,7 @@ func (self *hostingContext) SetCloseCallback(f func()) { func (self *hostingContext) OnClose() { log := pfxlog.Logger().WithField("service", self.service.Name) for _, addr := range self.config.AllowedSourceAddresses { - _, ipNet, err := utils.GetDialIP(addr) + ipNet, err := utils.GetCidr(addr) if err != nil { log.WithError(err).Error("failed to get dial IP") } else if self.addrTracker.RemoveAddress(ipNet.String()) { diff --git a/tunnel/intercept/interceptor.go b/tunnel/intercept/interceptor.go index 1088ea1da..b7b5b8fde 100644 --- a/tunnel/intercept/interceptor.go +++ b/tunnel/intercept/interceptor.go @@ -43,12 +43,13 @@ type Interceptor interface { // - service name - when a service is removed (e.g. from an appwan) type InterceptAddress struct { - cidr *net.IPNet - lowPort uint16 - highPort uint16 - protocol string - TproxySpec []string - AcceptSpec []string + cidr *net.IPNet + routeRequired bool + lowPort uint16 + highPort uint16 + protocol string + TproxySpec []string + AcceptSpec []string } func (addr *InterceptAddress) Proto() string { @@ -59,6 +60,10 @@ func (addr *InterceptAddress) IpNet() *net.IPNet { return addr.cidr } +func (addr *InterceptAddress) RouteRequired() bool { + return addr.routeRequired +} + func (addr *InterceptAddress) LowPort() uint16 { return addr.lowPort } @@ -82,14 +87,15 @@ type InterceptAddrCB interface { func GetInterceptAddresses(service *entities.Service, protocols []string, resolver dns.Resolver, addressCB InterceptAddrCB) error { for _, addr := range service.InterceptV1Config.Addresses { - err := getInterceptIP(service, addr, resolver, func(ip net.IP, ipNet *net.IPNet) { + err := getInterceptIP(service, addr, resolver, func(ipNet *net.IPNet, routeRequired bool) { for _, protocol := range protocols { for _, portRange := range service.InterceptV1Config.PortRanges { addr := &InterceptAddress{ - cidr: ipNet, - lowPort: portRange.Low, - highPort: portRange.High, - protocol: protocol} + cidr: ipNet, + routeRequired: routeRequired, + lowPort: portRange.Low, + highPort: portRange.High, + protocol: protocol} addressCB.Apply(addr) } } diff --git a/tunnel/intercept/iputils.go b/tunnel/intercept/iputils.go index d0e2f4d77..74888475c 100644 --- a/tunnel/intercept/iputils.go +++ b/tunnel/intercept/iputils.go @@ -17,85 +17,121 @@ package intercept import ( + "container/list" "fmt" + "github.com/gaissmai/extnetip" "github.com/michaelquigley/pfxlog" "github.com/openziti/ziti/tunnel/dns" "github.com/openziti/ziti/tunnel/entities" "github.com/openziti/ziti/tunnel/utils" "net" + "net/netip" + "sync" ) -var dnsIpLow, dnsIpHigh net.IP +var dnsPrefix netip.Prefix +var dnsCurrentIp netip.Addr +var dnsCurrentIpMtx sync.Mutex +var dnsRecycledIps *list.List func SetDnsInterceptIpRange(cidr string) error { - ip, ipnet, err := net.ParseCIDR(cidr) + prefix, err := netip.ParsePrefix(cidr) if err != nil { return fmt.Errorf("invalid cidr %s: %v", cidr, err) } - var ips []net.IP - for ip = ip.Mask(ipnet.Mask); ipnet.Contains(ip); utils.IncIP(ip) { - a := make(net.IP, len(ip)) - copy(a, ip) - ips = append(ips, a) - } + dnsPrefix = prefix + // get last ip in range for logging + _, dnsIpHigh := extnetip.Range(dnsPrefix) - // remove network address and broadcast address - dnsIpLow = ips[1] - dnsIpHigh = ips[len(ips)-2] + dnsCurrentIpMtx.Lock() + dnsCurrentIp = dnsPrefix.Addr() + dnsRecycledIps = list.New() + dnsCurrentIpMtx.Unlock() + pfxlog.Logger().Infof("dns intercept IP range: %v - %v", dnsCurrentIp, dnsIpHigh) + return nil +} - if len(dnsIpLow) != len(dnsIpHigh) { - return fmt.Errorf("lower dns IP length %d differs from upper dns IP length %d", len(dnsIpLow), len(dnsIpHigh)) +func GetDnsInterceptIpRange() *net.IPNet { + if !dnsPrefix.IsValid() { + if err := SetDnsInterceptIpRange("100.64.0.1/10"); err != nil { + pfxlog.Logger().WithError(err).Errorf("Failed to set DNS intercept range") + } + } + return &net.IPNet{ + IP: dnsPrefix.Addr().AsSlice(), + Mask: net.CIDRMask(dnsPrefix.Bits(), dnsPrefix.Addr().BitLen()), } - - pfxlog.Logger().Infof("dns intercept IP range: %s - %s", dnsIpLow.String(), dnsIpHigh.String()) - return nil } func cleanUpFunc(hostname string, resolver dns.Resolver) func() { f := func() { - if err := resolver.RemoveHostname(hostname); err != nil { - pfxlog.Logger().WithError(err).Errorf("failed to remove host mapping from resolver: %v ", hostname) + ip := resolver.RemoveHostname(hostname) + if ip != nil { + dnsCurrentIpMtx.Lock() + defer dnsCurrentIpMtx.Unlock() + addr, _ := netip.AddrFromSlice(ip) + dnsRecycledIps.PushBack(addr) } } return f } -func getInterceptIP(svc *entities.Service, hostname string, resolver dns.Resolver, addrCB func(net.IP, *net.IPNet)) error { +func getDnsIp(host string, addrCB func(*net.IPNet, bool), svc *entities.Service, resolver dns.Resolver) (net.IP, error) { + dnsCurrentIpMtx.Lock() + defer dnsCurrentIpMtx.Unlock() + var ip netip.Addr + + // look for returned IPs first + if dnsRecycledIps.Len() > 0 { + e := dnsRecycledIps.Front() + ip = e.Value.(netip.Addr) + dnsRecycledIps.Remove(e) + pfxlog.Logger().Debugf("using recycled ip %v for hostname %s", ip, host) + } else { + ip = dnsCurrentIp.Next() + if ip.IsValid() && dnsPrefix.Contains(ip) { + dnsCurrentIp = ip + } else { + return nil, fmt.Errorf("cannot allocate ip address: ip range exhausted") + } + } + + addr := &net.IPNet{IP: ip.AsSlice(), Mask: net.CIDRMask(ip.BitLen(), ip.BitLen())} + addrCB(addr, false) // no route is needed because the dns cidr was added to "lo" at startup + svc.AddCleanupAction(cleanUpFunc(host, resolver)) + return ip.AsSlice(), nil +} + +func getInterceptIP(svc *entities.Service, hostname string, resolver dns.Resolver, addrCB func(*net.IPNet, bool)) error { logger := pfxlog.Logger() + // handle wildcard domain - IPs will be allocated when matching hostnames are queried if hostname[0] == '*' { err := resolver.AddDomain(hostname, func(host string) (net.IP, error) { - var ip net.IP - var err error - ip, err = utils.NextIP(dnsIpLow, dnsIpHigh) - - if err == nil { - addrCB(ip, utils.Ip2IPnet(ip)) - svc.AddCleanupAction(cleanUpFunc(host, resolver)) - } - return ip, err + return getDnsIp(host, addrCB, svc, resolver) }) + if err == nil { + svc.AddCleanupAction(func() { resolver.RemoveDomain(hostname) }) + } return err } - ip, ipNet, err := utils.GetDialIP(hostname) + // handle IP or CIDR + ipNet, err := utils.GetCidr(hostname) if err == nil { - addrCB(ip, ipNet) + addrCB(ipNet, true) return err } - ip, _ = utils.NextIP(dnsIpLow, dnsIpHigh) - if ip == nil { + // handle hostnames + ip, err := getDnsIp(hostname, addrCB, svc, resolver) + if err != nil { return fmt.Errorf("invalid IP address or unresolvable hostname: %s", hostname) } if err = resolver.AddHostname(hostname, ip); err != nil { logger.WithError(err).Errorf("failed to add host/ip mapping to resolver: %v -> %v", hostname, ip) } - svc.AddCleanupAction(cleanUpFunc(hostname, resolver)) - - ipNet = utils.Ip2IPnet(ip) - addrCB(ip, ipNet) return nil } diff --git a/tunnel/intercept/svcpoll.go b/tunnel/intercept/svcpoll.go index 256fecfa8..0136ab1a7 100644 --- a/tunnel/intercept/svcpoll.go +++ b/tunnel/intercept/svcpoll.go @@ -43,6 +43,7 @@ var sourceIpVar = "$" + tunnel.SourceIpKey var sourcePortVar = "$" + tunnel.SourcePortKey var dstIpVar = "$" + tunnel.DestinationIpKey var destPortVar = "$" + tunnel.DestinationPortKey +var destHostnameVar = "$" + tunnel.DestinationHostname func NewServiceListenerGroup(interceptor Interceptor, resolver dns.Resolver) *ServiceListenerGroup { return &ServiceListenerGroup{ @@ -370,6 +371,12 @@ func (self *ServiceListener) getTemplatingProvider(template string) (entities.Te result = strings.ReplaceAll(result, sourcePortVar, sourceAddrPort) result = strings.ReplaceAll(result, dstIpVar, destAddrIp) result = strings.ReplaceAll(result, destPortVar, destAddrPort) + if self.resolver != nil { + destHostname, err := self.resolver.Lookup(net.ParseIP(destAddrIp)) + if err == nil { + result = strings.ReplaceAll(result, destHostnameVar, destHostname) + } + } return result }, nil } diff --git a/tunnel/intercept/tproxy/tproxy_linux.go b/tunnel/intercept/tproxy/tproxy_linux.go index 6a8149acd..fa126ac57 100644 --- a/tunnel/intercept/tproxy/tproxy_linux.go +++ b/tunnel/intercept/tproxy/tproxy_linux.go @@ -103,6 +103,13 @@ func New(config Config) (intercept.Interceptor, error) { log.Infof("tproxy config: udpIdleTimeout = [%s]", self.udpIdleTimeout.String()) log.Infof("tproxy config: udpCheckInterval = [%s]", self.udpCheckInterval.String()) + dnsNet := intercept.GetDnsInterceptIpRange() + err := router.AddLocalAddress(dnsNet, "lo") + if err != nil { + log.WithError(err).Errorf("unable to add %v to lo", dnsNet) + return nil, err + } + if self.diverter != "" { cmd := exec.Command(self.diverter, "-V") out, err := cmd.CombinedOutput() @@ -137,7 +144,7 @@ func New(config Config) (intercept.Interceptor, error) { logrus.Infof("no lan interface specified with '-lanIf'. please ensure firewall accepts intercepted service addresses") } - return self, nil + return self, err } type alwaysRemoveAddressTracker struct{} @@ -164,6 +171,11 @@ func (self *interceptor) Stop() { }) self.serviceProxies.Clear() self.cleanupChains() + dnsNet := intercept.GetDnsInterceptIpRange() + err := router.RemoveLocalAddress(dnsNet, "lo") + if err != nil { + logrus.WithError(err).Errorf("failed to remove route for dns IP range '%v' on 'lo'", dnsNet) + } } func (self *interceptor) Intercept(service *entities.Service, resolver dns.Resolver, tracker intercept.AddressTracker) error { @@ -505,10 +517,11 @@ func (self *tProxy) intercept(service *entities.Service, resolver dns.Resolver, func (self *tProxy) addInterceptAddr(interceptAddr *intercept.InterceptAddress, service *entities.Service, port IPPortAddr, tracker intercept.AddressTracker) error { ipNet := interceptAddr.IpNet() - if err := router.AddLocalAddress(ipNet, "lo"); err != nil { - return errors.Wrapf(err, "failed to add local route %v", ipNet) + if interceptAddr.RouteRequired() { + if err := router.AddLocalAddress(ipNet, "lo"); err != nil { + return errors.Wrapf(err, "failed to add local route %v", ipNet) + } } - tracker.AddAddress(ipNet.String()) self.addresses = append(self.addresses, interceptAddr) if self.interceptor.diverter != "" { @@ -608,23 +621,12 @@ func (self *tProxy) StopIntercepting(tracker intercept.AddressTracker) error { } ipNet := addr.IpNet() - if tracker.RemoveAddress(ipNet.String()) { - err := router.RemoveLocalAddress(ipNet, "lo") - if err != nil { - errorList = append(errorList, err) - log.WithError(err).Errorf("failed to remove route %v for service %s", ipNet, *self.service.Name) - } else { - host, hostErr := self.resolver.Lookup(ipNet.IP) - if hostErr == nil { - hostErr = self.resolver.RemoveHostname(host) - if hostErr == nil { - log.Debugf("Removed hostname: %v from Resolver", host) - } else { - log.Debugf("Could not remove hostname: %v from Resolver", host) - } - } else { - log.Debugf("failed to find resolver entry for %v in service %s", - ipNet, *self.service.Name) + if addr.RouteRequired() { + if tracker.RemoveAddress(ipNet.String()) { + err := router.RemoveLocalAddress(ipNet, "lo") + if err != nil { + errorList = append(errorList, err) + log.WithError(err).Errorf("failed to remove route %v for service %s", ipNet, *self.service.Name) } } } diff --git a/tunnel/utils/ipcalc.go b/tunnel/utils/ipcalc.go index c501ec2d5..46de704e1 100644 --- a/tunnel/utils/ipcalc.go +++ b/tunnel/utils/ipcalc.go @@ -17,85 +17,27 @@ package utils import ( - "bytes" - "github.com/michaelquigley/pfxlog" - "github.com/pkg/errors" + "fmt" "net" + "net/netip" ) -func IsLocallyAssigned(addr, lower, upper net.IP) bool { - return bytes.Compare(addr.To16(), lower.To16()) >= 0 && bytes.Compare(addr.To16(), upper.To16()) <= 0 -} - -// return the next available IP address in the range of provided IPs -func NextIP(lower, upper net.IP) (net.IP, error) { - usedAddrs, err := AllInterfaceAddrs() - if err != nil { - return nil, err - } - - // need to make a copy of lower net.IP, since they're just byte arrays. Otherwise - // we're continually changing the lower ip globally - ip := net.IP(make([]byte, len(lower))) - copy(ip, lower) - - for ; !ip.Equal(upper); IncIP(ip) { - inUse := false - for _, usedAddr := range usedAddrs { - usedIP, _, _ := net.ParseCIDR(usedAddr.String()) - if ip.Equal(usedIP) { - inUse = true - break - } - } - if !inUse { - return ip, nil - } - } - - return nil, nil -} - -func IncIP(ip net.IP) { - for i := len(ip) - 1; i >= 0; i-- { - ip[i]++ - if ip[i] > 0 { - break - } - } -} - -// Return the length of a full prefix (no subnetting) for the given IP address. -// Returns 32 for ipv4 addresses, and 128 for ipv6 addresses. -func AddrBits(ip net.IP) int { - if ip == nil { - return 0 - } else if ip.To4() != nil { - return net.IPv4len * 8 - } else if ip.To16() != nil { - return net.IPv6len * 8 - } - - pfxlog.Logger().Infof("invalid IP address %s", ip.String()) - return 0 -} - -func Ip2IPnet(ip net.IP) *net.IPNet { - prefixLen := AddrBits(ip) - ipNet := &net.IPNet{IP: ip, Mask: net.CIDRMask(prefixLen, prefixLen)} - return ipNet -} - -func GetDialIP(addr string) (net.IP, *net.IPNet, error) { - // hostname is an ip address, return it - if parsedIP := net.ParseIP(addr); parsedIP != nil { - ipNet := Ip2IPnet(parsedIP) - return parsedIP, ipNet, nil +func GetCidr(ipOrCidr string) (*net.IPNet, error) { + ip, err := netip.ParseAddr(ipOrCidr) + if err == nil { + return &net.IPNet{ + IP: ip.AsSlice(), + Mask: net.CIDRMask(ip.BitLen(), ip.BitLen()), + }, nil } - if parsedIP, cidr, err := net.ParseCIDR(addr); err == nil { - return parsedIP, cidr, nil + pfx, err := netip.ParsePrefix(ipOrCidr) + if err == nil { + return &net.IPNet{ + IP: pfx.Addr().AsSlice(), + Mask: net.CIDRMask(pfx.Bits(), pfx.Addr().BitLen()), + }, nil } - return nil, nil, errors.Errorf("could not parse '%s' as IP or CIDR", addr) + return nil, fmt.Errorf("failed to parse '%v' as IP or CIDR", ipOrCidr) } diff --git a/zititest/go.mod b/zititest/go.mod index f020add91..d8a172c53 100644 --- a/zititest/go.mod +++ b/zititest/go.mod @@ -64,6 +64,7 @@ require ( github.com/felixge/httpsnoop v1.0.3 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa // indirect + github.com/gaissmai/extnetip v0.4.0 // indirect github.com/go-acme/lego/v4 v4.15.0 // indirect github.com/go-jose/go-jose/v3 v3.0.1 // indirect github.com/go-logr/logr v1.4.1 // indirect diff --git a/zititest/go.sum b/zititest/go.sum index 0d8686336..2cdc64793 100644 --- a/zititest/go.sum +++ b/zititest/go.sum @@ -197,6 +197,8 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa h1:RDBNVkRviHZtvDvId8XSGPu3rmpmSe+wKRcEWNgsfWU= github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= +github.com/gaissmai/extnetip v0.4.0 h1:9pNd/Z6QSlkda35bug/IYuPYaPMTYRuqcxPce5Z9TTQ= +github.com/gaissmai/extnetip v0.4.0/go.mod h1:M3NWlyFKaVosQXWXKKeIPK+5VM4U85DahdIqNYX4TK4= github.com/getkin/kin-openapi v0.13.0/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s+pcEVXFuAjw= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=