Add region-dns service

This commit is contained in:
Zexi Li
2018-08-02 02:11:25 +08:00
parent 4bbafa8bc7
commit 9ac3e1a19d
7 changed files with 720 additions and 3 deletions
+91
View File
@@ -0,0 +1,91 @@
package main
import (
_ "github.com/coredns/coredns/plugin/auto"
_ "github.com/coredns/coredns/plugin/autopath"
_ "github.com/coredns/coredns/plugin/bind"
_ "github.com/coredns/coredns/plugin/cache"
_ "github.com/coredns/coredns/plugin/chaos"
_ "github.com/coredns/coredns/plugin/debug"
_ "github.com/coredns/coredns/plugin/dnssec"
_ "github.com/coredns/coredns/plugin/dnstap"
_ "github.com/coredns/coredns/plugin/erratic"
_ "github.com/coredns/coredns/plugin/errors"
_ "github.com/coredns/coredns/plugin/etcd"
_ "github.com/coredns/coredns/plugin/federation"
_ "github.com/coredns/coredns/plugin/file"
_ "github.com/coredns/coredns/plugin/forward"
_ "github.com/coredns/coredns/plugin/health"
_ "github.com/coredns/coredns/plugin/hosts"
_ "github.com/coredns/coredns/plugin/kubernetes"
_ "github.com/coredns/coredns/plugin/loadbalance"
_ "github.com/coredns/coredns/plugin/log"
_ "github.com/coredns/coredns/plugin/metrics"
_ "github.com/coredns/coredns/plugin/nsid"
_ "github.com/coredns/coredns/plugin/pprof"
_ "github.com/coredns/coredns/plugin/proxy"
_ "github.com/coredns/coredns/plugin/reload"
_ "github.com/coredns/coredns/plugin/rewrite"
_ "github.com/coredns/coredns/plugin/root"
_ "github.com/coredns/coredns/plugin/route53"
_ "github.com/coredns/coredns/plugin/secondary"
_ "github.com/coredns/coredns/plugin/template"
_ "github.com/coredns/coredns/plugin/tls"
_ "github.com/coredns/coredns/plugin/trace"
_ "github.com/coredns/coredns/plugin/whoami"
_ "github.com/mholt/caddy/onevent"
_ "github.com/mholt/caddy/startupshutdown"
"github.com/coredns/coredns/core/dnsserver"
"github.com/coredns/coredns/coremain"
_ "github.com/yunionio/onecloud/pkg/dns"
)
var directives = []string{
"tls",
"reload",
"nsid",
"root",
"bind",
"debug",
"trace",
"health",
"pprof",
"prometheus",
"errors",
"log",
"dnstap",
"chaos",
"loadbalance",
"cache",
"rewrite",
"dnssec",
"autopath",
"template",
"hosts",
"route53",
"federation",
"kubernetes",
"file",
"auto",
"secondary",
"etcd",
"redis",
"forward",
"proxy",
"erratic",
"whoami",
"on",
"startup",
"shutdown",
"yunion",
}
func init() {
dnsserver.Directives = directives
}
func main() {
coremain.Run()
}
+67 -3
View File
@@ -11,12 +11,10 @@ import (
"github.com/yunionio/jsonutils"
"github.com/yunionio/log"
"github.com/yunionio/onecloud/pkg/mcclient"
"github.com/yunionio/onecloud/pkg/mcclient/auth"
"github.com/yunionio/onecloud/pkg/httperrors"
"github.com/yunionio/pkg/tristate"
"github.com/yunionio/pkg/util/compare"
"github.com/yunionio/pkg/util/fileutils"
"github.com/yunionio/pkg/util/netutils"
"github.com/yunionio/pkg/util/osprofile"
"github.com/yunionio/pkg/util/regutils"
"github.com/yunionio/pkg/util/sysutils"
@@ -31,6 +29,9 @@ import (
"github.com/yunionio/onecloud/pkg/cloudcommon/notifyclient"
"github.com/yunionio/onecloud/pkg/cloudprovider"
"github.com/yunionio/onecloud/pkg/compute/options"
"github.com/yunionio/onecloud/pkg/httperrors"
"github.com/yunionio/onecloud/pkg/mcclient"
"github.com/yunionio/onecloud/pkg/mcclient/auth"
)
const (
@@ -996,6 +997,16 @@ func (self *SGuest) getRealIPs() []string {
return ips
}
func (self *SGuest) IsExitOnly() bool {
for _, ip := range self.getRealIPs() {
addr, _ := netutils.NewIPV4Addr(ip)
if !netutils.IsExitAddress(addr) {
return false
}
}
return true
}
func (self *SGuest) getVirtualIPs() []string {
ips := make([]string, 0)
for _, guestgroup := range self.GetGroups() {
@@ -2552,3 +2563,56 @@ func (self *SGuest) GetKeypairPublicKey() string {
func (model *SGuest) AllowPerformCancelDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
return userCred.IsSystemAdmin()
}
func (manager *SGuestManager) GetIpInProjectWithName(projectId, name string, isExitOnly bool) []string {
guestnics := GuestnetworkManager.Query().SubQuery()
guests := manager.Query().SubQuery()
networks := NetworkManager.Query().SubQuery()
q := guestnics.Query(guestnics.Field("ip_addr")).Join(guests,
sqlchemy.AND(
sqlchemy.Equals(guests.Field("id"), guestnics.Field("guest_id")),
sqlchemy.OR(sqlchemy.IsNull(guests.Field("pending_deleted")),
sqlchemy.IsFalse(guests.Field("pending_deleted"))),
sqlchemy.IsFalse(guests.Field("deleted")))).
Join(networks, sqlchemy.AND(sqlchemy.Equals(networks.Field("id"), guestnics.Field("network_id")),
sqlchemy.IsFalse(networks.Field("deleted")))).
Filter(sqlchemy.Equals(guests.Field("name"), name)).
Filter(sqlchemy.NotEquals(guestnics.Field("ip_addr"), "")).
Filter(sqlchemy.IsNotNull(guestnics.Field("ip_addr"))).
Filter(sqlchemy.IsNotNull(networks.Field("guest_gateway")))
ips := make([]string, 0)
rows, err := q.Rows()
if err != nil {
log.Errorf("Get guest ip with name query err: %v", err)
return ips
}
for rows.Next() {
var ip string
err = rows.Scan(&ip)
if err != nil {
log.Errorf("Get guest ip with name scan err: %v", err)
return ips
}
ips = append(ips, ip)
}
return manager.getIpsByExit(ips, isExitOnly)
}
func (manager *SGuestManager) getIpsByExit(ips []string, isExitOnly bool) []string {
intRet := make([]string, 0)
extRet := make([]string, 0)
for _, ip := range ips {
addr, _ := netutils.NewIPV4Addr(ip)
if netutils.IsExitAddress(addr) {
extRet = append(extRet, ip)
continue
}
intRet = append(intRet, ip)
}
if isExitOnly {
return extRet
} else if len(intRet) > 0 {
return intRet
}
return extRet
}
+347
View File
@@ -0,0 +1,347 @@
package dns
import (
"context"
"database/sql"
"errors"
"fmt"
//"os"
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/etcd/msg"
"github.com/coredns/coredns/plugin/pkg/dnsutil"
"github.com/coredns/coredns/plugin/pkg/fall"
"github.com/coredns/coredns/request"
_ "github.com/go-sql-driver/mysql"
//clog "github.com/coredns/coredns/plugin/log"
"github.com/coredns/coredns/plugin/pkg/upstream"
//"github.com/coredns/coredns/plugin/pkg/replacer"
"github.com/mholt/caddy"
"github.com/miekg/dns"
ylog "github.com/yunionio/log"
"github.com/yunionio/pkg/utils"
"github.com/yunionio/sqlchemy"
"github.com/yunionio/onecloud/pkg/cloudcommon/db"
"github.com/yunionio/onecloud/pkg/compute/models"
)
const (
PluginName string = "yunion"
)
var (
DNSTypeMap map[uint16]string = map[uint16]string{
dns.TypeA: "A",
dns.TypeAAAA: "AAAA",
dns.TypeTXT: "TXT",
dns.TypeCNAME: "CNAME",
dns.TypePTR: "PTR",
dns.TypeMX: "MX",
dns.TypeSRV: "SRV",
dns.TypeSOA: "SOA",
dns.TypeNS: "NS",
}
)
type SRegionDNS struct {
Next plugin.Handler
Fall fall.F
Zones []string
Upstream upstream.Upstream
SqlConnection string
K8sConfigFile string
}
func New() *SRegionDNS {
r := new(SRegionDNS)
return r
}
func (r *SRegionDNS) initDB(c *caddy.Controller) error {
dialect, sqlStr, err := utils.TransSQLAchemyURL(r.SqlConnection)
if err != nil {
return err
}
dbConn, err := sql.Open(dialect, sqlStr)
if err != nil {
return err
}
sqlchemy.SetDB(dbConn)
db.InitAllManagers()
c.OnShutdown(func() error {
r.CloseDB()
return nil
})
return nil
}
func (r *SRegionDNS) initK8s(c *caddy.Controller) error {
return nil
}
func (r *SRegionDNS) CloseDB() {
sqlchemy.CloseDB()
}
func (r *SRegionDNS) ServeDNS(ctx context.Context, w dns.ResponseWriter, rmsg *dns.Msg) (int, error) {
//rrw := dnstest.NewRecorder(w)
//rep := replacer.New(r, rrw, corelog.CommonLogEmptyValue)
//log.Infof("%v", rep.Replace(format))
//fmt.Fprintln(output, rep.Replace(format))
//count := models.DnsRecordManager.QueryDns("", "drone.yunion.io")
var (
records []dns.RR
extra []dns.RR
err error
)
opt := plugin.Options{}
state := request.Request{W: w, Req: rmsg, Context: ctx}
//isMyDomain := true
zone := plugin.Zones(r.Zones).Matches(state.Name())
if zone == "" {
//isMyDomain = false
return plugin.NextOrFailure(r.Name(), r.Next, ctx, w, rmsg)
}
switch state.QType() {
case dns.TypeA:
ylog.Debugf("===A question: %#v", state)
records, err = plugin.A(r, zone, state, nil, opt)
case dns.TypeAAAA:
ylog.Debugf("===AAAA question: %#v", state)
records, err = plugin.AAAA(r, zone, state, nil, opt)
case dns.TypeTXT:
ylog.Debugf("===TXT question: %#v", state)
records, err = plugin.TXT(r, zone, state, opt)
case dns.TypeCNAME:
ylog.Debugf("===CNAME question: %#v", state)
records, err = plugin.CNAME(r, zone, state, opt)
case dns.TypePTR:
ylog.Debugf("===PTR question: %#v", state)
records, err = plugin.PTR(r, zone, state, opt)
case dns.TypeMX:
ylog.Debugf("===MX question: %#v", state)
records, extra, err = plugin.MX(r, zone, state, opt)
case dns.TypeSRV:
ylog.Debugf("===SRV question: %#v", state)
records, extra, err = plugin.SRV(r, zone, state, opt)
case dns.TypeSOA:
ylog.Debugf("===SOA question: %#v", state)
records, err = plugin.SOA(r, zone, state, opt)
case dns.TypeNS:
ylog.Debugf("===NS question: %#v", state)
if state.Name() == zone {
records, extra, err = plugin.NS(r, zone, state, opt)
break
}
fallthrough
default:
ylog.Infof("=== not processed state: %#v", state)
// Do a fake A lookup, so we can distinguish between NODATA and NXDOMAIN
_, err = plugin.A(r, zone, state, nil, opt)
}
if r.IsNameError(err) {
if r.Fall.Through(state.Name()) {
return plugin.NextOrFailure(r.Name(), r.Next, ctx, w, rmsg)
}
return plugin.BackendError(r, zone, dns.RcodeNameError, state, nil /* err */, opt)
}
if err != nil {
//return dns.RcodeServerFailure, err
return plugin.BackendError(r, zone, dns.RcodeServerFailure, state, err, opt)
}
if len(records) == 0 {
return plugin.BackendError(r, zone, dns.RcodeSuccess, state, err, opt)
}
m := new(dns.Msg)
m.SetReply(rmsg)
m.Authoritative, m.RecursionAvailable = true, true
m.Answer = append(m.Answer, records...)
m.Extra = append(m.Extra, extra...)
state.SizeAndDo(m)
m, _ = state.Scrub(m)
w.WriteMsg(m)
return dns.RcodeSuccess, nil
}
var (
errNoItems = errors.New("no items found")
)
// Services implements the ServiceBackend interface
func (r *SRegionDNS) Services(state request.Request, exact bool, opt plugin.Options) (services []msg.Service, err error) {
//services, err = r.Records(state, exact)
//if err != nil {
//return
//}
//services = msg.Group
switch state.QType() {
case dns.TypeTXT:
t, _ := dnsutil.TrimZone(state.Name(), state.Zone)
segs := dns.SplitDomainName(t)
if len(segs) != 1 {
return nil, fmt.Errorf("yunion region: TXT query can onlyu be for dns-version: %s", state.QName())
}
if segs[0] != "dns-version" {
return nil, nil
}
svc := msg.Service{Text: "0.0.1", TTL: 28800, Key: msg.Path(state.QName(), "coredns")}
return []msg.Service{svc}, nil
case dns.TypeNS:
ns := r.nsAddr()
svc := msg.Service{Host: ns.A.String(), Key: msg.Path(state.QName(), "coredns")}
return []msg.Service{svc}, nil
}
if state.QType() == dns.TypeA && isDefaultNS(state.Name(), state.Zone) {
// If this is an A request for "ns.dns", respond with a "fake" record for coredns.
// SOA records always use this hardcoded name
ns := r.nsAddr()
svc := msg.Service{Host: ns.A.String(), Key: msg.Path(state.QName(), "coredns")}
return []msg.Service{svc}, nil
}
s, e := r.Records(state, false)
// SRV is not yet implemented, so remove those records.
if state.QType() != dns.TypeSRV {
return s, e
}
internal := []msg.Service{}
for _, svc := range s {
if t, _ := svc.HostType(); t != dns.TypeCNAME {
internal = append(internal, svc)
}
}
return internal, e
}
// Reverse implements the ServiceBackend interface
func (r *SRegionDNS) Reverse(state request.Request, exact bool, opt plugin.Options) (services []msg.Service, err error) {
return r.Services(state, exact, opt)
}
// Lookup implements the ServiceBackend interface
func (r *SRegionDNS) Lookup(state request.Request, name string, typ uint16) (*dns.Msg, error) {
return r.Upstream.Lookup(state, name, typ)
}
// IsNameError implements the ServiceBackend interface
func (r *SRegionDNS) IsNameError(err error) bool {
return err == errNoItems
}
// Records looks up records in region mysql
func (r *SRegionDNS) Records(state request.Request, exact bool) ([]msg.Service, error) {
req, e := parseRequest(state)
if e != nil {
return nil, e
}
return r.findRecords(req)
}
func (r *SRegionDNS) getGuestIpWithName(req *recordRequest) []string {
ips := []string{}
name := req.GuestName()
projectId := req.ProjectId()
isExitOnly := req.IsExitOnly()
ylog.Warningf("===========args: %q, %q, %v", projectId, name, isExitOnly)
ips = models.GuestManager.GetIpInProjectWithName(projectId, name, isExitOnly)
return ips
}
func (r *SRegionDNS) Name() string {
return PluginName
}
type SGuestInfo struct {
*models.SGuest
}
func (g *SGuestInfo) GetProjectId() string {
return g.SGuest.ProjectId
}
func (g *SGuestInfo) GetGuestId() string {
return g.SGuest.Id
}
func (g *SGuestInfo) IsExitOnly() bool {
return g.SGuest.IsExitOnly()
}
func NewGuestInfoByAddress(address string) *SGuestInfo {
guest := models.GuestnetworkManager.GetGuestByAddress(address)
if guest == nil {
return nil
}
return &SGuestInfo{SGuest: guest}
}
func (r *SRegionDNS) findRecords(req *recordRequest) (recs []msg.Service, err error) {
isMyDomain := false
zone := plugin.Zones(r.Zones).Matches(req.state.Name())
if zone != "" {
isMyDomain = true
}
//isPrivateAddr := false
if isMyDomain {
return r.findInternalRecords(req)
}
return r.findExternalRecords(req)
}
func (r *SRegionDNS) findInternalRecords(req *recordRequest) ([]msg.Service, error) {
ylog.Debugf("=======findInternalRecords, srcip: %q, projectId: %q", req.SrcIP4(), req.ProjectId())
if req.ProjectId() == "" {
return nil, nil
}
// first, try dns records
svcs, _ := r.findLocalRecords(req)
if len(svcs) != 0 {
return svcs, nil
}
// second, try guest table
ips := r.getGuestIpWithName(req)
return ips2DnsRecords(ips), nil
}
func (r *SRegionDNS) findExternalRecords(req *recordRequest) ([]msg.Service, error) {
srcIP := req.SrcIP4()
ylog.Debugf("Get client ip: %q", srcIP)
return r.findLocalRecords(req)
}
func (r *SRegionDNS) findLocalRecords(req *recordRequest) (recs []msg.Service, err error) {
ips := models.DnsRecordManager.QueryDnsIps(req.ProjectId(), req.Name(), req.Type())
if len(ips) == 0 {
err = errNoItems
return
}
for _, ip := range ips {
s := msg.Service{Host: ip.Addr, TTL: 5 * 60}
recs = append(recs, s)
}
return
}
func ips2DnsRecords(ips []string) []msg.Service {
recs := make([]msg.Service, 0)
for _, ip := range ips {
s := msg.Service{Host: ip, TTL: 5 * 60}
recs = append(recs, s)
}
return recs
}
+19
View File
@@ -0,0 +1,19 @@
package dns
import (
//"net"
"strings"
"github.com/miekg/dns"
)
const defaultNSName = "ns.dns."
func isDefaultNS(name, zone string) bool {
return strings.Index(name, defaultNSName) == 0 && strings.Index(name, zone) == len(defaultNSName)
}
func (r *SRegionDNS) nsAddr() *dns.A {
//rr := new(dns.A)
return nil
}
+71
View File
@@ -0,0 +1,71 @@
package dns
import (
"strings"
"github.com/coredns/coredns/plugin/pkg/dnsutil"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
"github.com/yunionio/log"
)
type recordRequest struct {
state request.Request
domainSegs []string
projectId string
guestInfo *SGuestInfo
}
func parseRequest(state request.Request) (r *recordRequest, err error) {
base, _ := dnsutil.TrimZone(state.Name(), state.Zone)
segs := dns.SplitDomainName(base)
r = &recordRequest{
state: state,
domainSegs: segs,
}
srcIP := r.SrcIP4()
guestInfo := NewGuestInfoByAddress(srcIP)
r.guestInfo = guestInfo
return
}
func (r recordRequest) Name() string {
//fullName, _ := dnsutil.TrimZone(r.state.Name(), "")
name := r.state.Name()
log.Errorf("==name: %q", name)
name = strings.TrimSuffix(name, ".")
return name
}
func (r recordRequest) GuestName() string {
seps := strings.Split(r.Name(), ".")
if len(seps) == 0 {
return ""
}
return seps[0]
}
func (r recordRequest) Type() string {
return DNSTypeMap[r.state.QType()]
}
func (r recordRequest) SrcIP4() string {
ip := r.state.IP()
log.Debugf("Source ip: %q, guestName: %q", ip, r.GuestName())
return ip
}
func (r recordRequest) ProjectId() string {
if r.guestInfo == nil {
return ""
}
return r.guestInfo.GetProjectId()
}
func (r recordRequest) IsExitOnly() bool {
if r.guestInfo == nil {
return false
}
return r.guestInfo.IsExitOnly()
}
+101
View File
@@ -0,0 +1,101 @@
package dns
import (
"os"
"github.com/coredns/coredns/core/dnsserver"
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/pkg/upstream"
"github.com/mholt/caddy"
"github.com/yunionio/log"
)
func init() {
caddy.RegisterPlugin(PluginName, caddy.Plugin{
ServerType: "dns",
Action: setup,
})
}
func setup(c *caddy.Controller) error {
os.Stderr = os.Stdout
rDNS, err := regionDNSParse(c)
if err != nil {
return plugin.Error(PluginName, err)
}
log.Infof("regionDNSParse succ: %#v", rDNS)
err = rDNS.initDB(c)
if err != nil {
return plugin.Error(PluginName, err)
}
err = rDNS.initK8s(c)
if err != nil {
return plugin.Error(PluginName, err)
}
dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
rDNS.Next = next
return rDNS
})
return nil
}
func regionDNSParse(c *caddy.Controller) (*SRegionDNS, error) {
return parseConfig(c)
}
func parseConfig(c *caddy.Controller) (*SRegionDNS, error) {
rDNS := New()
for c.Next() {
rDNS.Zones = c.RemainingArgs()
if len(rDNS.Zones) == 0 {
rDNS.Zones = make([]string, len(c.ServerBlockKeys))
copy(rDNS.Zones, c.ServerBlockKeys)
}
for i, str := range rDNS.Zones {
rDNS.Zones[i] = plugin.Host(str).Normalize()
}
log.Warningf("==zones: %v", rDNS.Zones)
if c.NextBlock() {
for {
log.Printf("===val: %v", c.Val())
switch c.Val() {
case "fallthrough":
rDNS.Fall.SetZonesFromArgs(c.RemainingArgs())
case "sql_connection":
if !c.NextArg() {
return nil, c.ArgErr()
}
rDNS.SqlConnection = c.Val()
case "kube_config":
if !c.NextArg() {
return nil, c.ArgErr()
}
rDNS.K8sConfigFile = c.Val()
case "upstream":
args := c.RemainingArgs()
u, err := upstream.New(args)
if err != nil {
return nil, err
}
rDNS.Upstream = u
default:
if c.Val() != "}" {
return nil, c.Errf("unknown property %q", c.Val())
}
}
if !c.Next() {
break
}
}
}
}
return rDNS, nil
}
+24
View File
@@ -0,0 +1,24 @@
package dns
import (
"context"
"time"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
// Serial implements the Transferer interface
func (r *SRegionDNS) Serial(state request.Request) uint32 {
return uint32(time.Now().Unix())
}
// MinTTL implements the Transferer interface
func (r *SRegionDNS) MinTTL(state request.Request) uint32 {
return 30
}
// Transferer implements the Transferer interface
func (r *SRegionDNS) Transfer(ctx context.Context, state request.Request) (int, error) {
return dns.RcodeServerFailure, nil
}