dns.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package dns
  15. import (
  16. "context"
  17. "fmt"
  18. "strconv"
  19. "strings"
  20. "github.com/coredns/caddy"
  21. "github.com/coredns/coredns/plugin"
  22. "github.com/coredns/coredns/plugin/etcd/msg"
  23. "github.com/coredns/coredns/plugin/pkg/dnsutil"
  24. "github.com/coredns/coredns/plugin/pkg/fall"
  25. "github.com/coredns/coredns/plugin/pkg/upstream"
  26. "github.com/coredns/coredns/request"
  27. "github.com/miekg/dns"
  28. "yunion.io/x/jsonutils"
  29. "yunion.io/x/pkg/errors"
  30. "yunion.io/x/sqlchemy"
  31. _ "yunion.io/x/sqlchemy/backends"
  32. api "yunion.io/x/onecloud/pkg/apis/compute"
  33. identity_api "yunion.io/x/onecloud/pkg/apis/identity"
  34. "yunion.io/x/onecloud/pkg/cloudcommon"
  35. "yunion.io/x/onecloud/pkg/cloudcommon/db"
  36. common_options "yunion.io/x/onecloud/pkg/cloudcommon/options"
  37. "yunion.io/x/onecloud/pkg/compute/models"
  38. "yunion.io/x/onecloud/pkg/mcclient"
  39. "yunion.io/x/onecloud/pkg/mcclient/auth"
  40. )
  41. const (
  42. PluginName string = "yunion"
  43. // defaultTTL to apply to all answers
  44. defaultTTL = 300
  45. defaultDbMaxOpenConn = 32
  46. defaultDbMaxIdleConn = 32
  47. )
  48. var (
  49. DNSTypeMap map[uint16]string = map[uint16]string{
  50. dns.TypeA: "A",
  51. dns.TypeAAAA: "AAAA",
  52. dns.TypeTXT: "TXT",
  53. dns.TypeCNAME: "CNAME",
  54. dns.TypePTR: "PTR",
  55. dns.TypeMX: "MX",
  56. dns.TypeSRV: "SRV",
  57. dns.TypeSOA: "SOA",
  58. dns.TypeNS: "NS",
  59. }
  60. )
  61. type SRegionDNS struct {
  62. Next plugin.Handler
  63. Fall fall.F
  64. Zones []string
  65. PrimaryZone string
  66. Upstream *upstream.Upstream
  67. SqlConnection string
  68. AuthUrl string
  69. AdminProject string
  70. AdminUser string
  71. AdminDomain string
  72. AdminPassword string
  73. Region string
  74. AdminProjectDomain string
  75. InCloudOnly bool
  76. // K8sSkip bool
  77. // K8sManager *k8s.SKubeClusterManager
  78. primaryZoneLabelCount int
  79. }
  80. func New() *SRegionDNS {
  81. r := &SRegionDNS{}
  82. return r
  83. }
  84. func (r *SRegionDNS) initDB(c *caddy.Controller) error {
  85. options := &common_options.DBOptions{
  86. SqlConnection: r.SqlConnection,
  87. }
  88. cloudcommon.InitDBConn(options)
  89. db.InitAllManagers()
  90. c.OnShutdown(func() error {
  91. sqlchemy.CloseDB()
  92. return nil
  93. })
  94. return nil
  95. }
  96. /*func (r *SRegionDNS) initK8s() {
  97. r.K8sManager = k8s.NewKubeClusterManager(r.Region, 30*time.Second)
  98. r.K8sManager.Start()
  99. }*/
  100. func (r *SRegionDNS) getAdminSession(ctx context.Context) *mcclient.ClientSession {
  101. return auth.GetAdminSession(ctx, r.Region)
  102. }
  103. func (r *SRegionDNS) initAuth() {
  104. if len(r.AdminDomain) == 0 {
  105. r.AdminDomain = identity_api.DEFAULT_DOMAIN_NAME
  106. }
  107. if len(r.AdminProjectDomain) == 0 {
  108. r.AdminProjectDomain = identity_api.DEFAULT_DOMAIN_NAME
  109. }
  110. authInfo := auth.NewAuthInfo(r.AuthUrl, r.AdminDomain, r.AdminUser, r.AdminPassword, r.AdminProject, r.AdminProjectDomain)
  111. auth.Init(authInfo, false, true, "", "")
  112. }
  113. func (r *SRegionDNS) ServeDNS(ctx context.Context, w dns.ResponseWriter, rmsg *dns.Msg) (int, error) {
  114. log.Debugf("ServeDNS: %s", jsonutils.Marshal(rmsg).String())
  115. var (
  116. records []dns.RR
  117. extra []dns.RR
  118. err error
  119. )
  120. state := request.Request{W: w, Req: rmsg}
  121. qname := state.Name()
  122. zone := plugin.Zones(r.Zones).Matches(qname)
  123. if zone == "" {
  124. return plugin.NextOrFailure(r.Name(), r.Next, ctx, w, rmsg)
  125. }
  126. state.Zone = zone
  127. opt := plugin.Options{}
  128. switch state.QType() {
  129. case dns.TypeA:
  130. records, _, err = plugin.A(ctx, r, zone, state, nil, opt)
  131. case dns.TypeAAAA:
  132. records, _, err = plugin.AAAA(ctx, r, zone, state, nil, opt)
  133. case dns.TypeTXT:
  134. records, _, err = plugin.TXT(ctx, r, zone, state, nil, opt)
  135. case dns.TypeCNAME:
  136. records, err = plugin.CNAME(ctx, r, zone, state, opt)
  137. case dns.TypePTR:
  138. records, err = plugin.PTR(ctx, r, zone, state, opt)
  139. case dns.TypeMX:
  140. records, extra, err = plugin.MX(ctx, r, zone, state, opt)
  141. case dns.TypeSRV:
  142. records, extra, err = plugin.SRV(ctx, r, zone, state, opt)
  143. case dns.TypeSOA:
  144. records, err = plugin.SOA(ctx, r, zone, state, opt)
  145. case dns.TypeNS:
  146. if state.Name() == zone {
  147. records, extra, err = plugin.NS(ctx, r, zone, state, opt)
  148. break
  149. }
  150. fallthrough
  151. default:
  152. log.Warningf("Not processed state: %#v", state)
  153. // Do a fake A lookup, so we can distinguish between NODATA and NXDOMAIN
  154. _, _, err = plugin.A(ctx, r, zone, state, nil, opt)
  155. }
  156. if err == errCallNext {
  157. if r.Fall.Through(state.Name()) {
  158. return plugin.NextOrFailure(r.Name(), r.Next, ctx, w, rmsg)
  159. }
  160. return plugin.BackendError(ctx, r, zone, dns.RcodeNameError, state, nil /* err */, opt)
  161. } else if err == errRefused {
  162. return plugin.BackendError(ctx, r, zone, dns.RcodeRefused, state, err, opt)
  163. } else if err == errNotFound {
  164. return plugin.BackendError(ctx, r, zone, dns.RcodeNameError, state, err, opt)
  165. }
  166. if len(records) == 0 {
  167. return plugin.BackendError(ctx, r, zone, dns.RcodeNameError, state, err, opt)
  168. }
  169. m := new(dns.Msg)
  170. m.SetReply(rmsg)
  171. m.Authoritative, m.RecursionAvailable = true, true
  172. m.Answer = append(m.Answer, records...)
  173. m.Extra = append(m.Extra, extra...)
  174. state.SizeAndDo(m)
  175. m = state.Scrub(m)
  176. w.WriteMsg(m)
  177. return dns.RcodeSuccess, nil
  178. }
  179. var (
  180. errRefused = errors.Error("refused the query")
  181. errNotFound = errors.Error("not found")
  182. errCallNext = errors.Error("continue to next")
  183. )
  184. // Services implements the ServiceBackend interface
  185. func (r *SRegionDNS) Services(ctx context.Context, state request.Request, exact bool, opt plugin.Options) ([]msg.Service, error) {
  186. var services []msg.Service
  187. var err error
  188. defer func() {
  189. if len(services) == 0 {
  190. log.Infof(`%s:%s %s - %d "%s %s empty response"`, state.RemoteAddr(), state.Port(), state.Proto(), state.Len(), state.Type(), state.Name())
  191. return
  192. }
  193. for _, service := range services {
  194. log.Infof(`%s:%s %s - %d "%s IN %s %s"`, state.RemoteAddr(), state.Port(), state.Proto(), state.Len(), state.Type(), state.Name(), jsonutils.Marshal(service).String())
  195. }
  196. }()
  197. switch state.QType() {
  198. case dns.TypeTXT:
  199. t, _ := dnsutil.TrimZone(state.Name(), state.Zone)
  200. segs := dns.SplitDomainName(t)
  201. if len(segs) != 1 {
  202. return nil, fmt.Errorf("yunion region: TXT query can onlyu be for dns-version: %s", state.QName())
  203. }
  204. if segs[0] != "dns-version" {
  205. return nil, nil
  206. }
  207. svc := msg.Service{Text: "0.0.1", TTL: 28800, Key: msg.Path(state.QName(), "coredns")}
  208. services = []msg.Service{svc}
  209. return services, nil
  210. case dns.TypeNS:
  211. ns := r.nsAddr()
  212. svc := msg.Service{Host: ns.A.String(), Key: msg.Path(state.QName(), "coredns")}
  213. services = []msg.Service{svc}
  214. return services, nil
  215. }
  216. if state.QType() == dns.TypeA && isDefaultNS(state.Name(), state.Zone) {
  217. // If this is an A request for "ns.dns", respond with a "fake" record for coredns.
  218. // SOA records always use this hardcoded name
  219. ns := r.nsAddr()
  220. svc := msg.Service{Host: ns.A.String(), Key: msg.Path(state.QName(), "coredns")}
  221. services = []msg.Service{svc}
  222. return services, nil
  223. }
  224. if _, ok := DNSTypeMap[state.QType()]; !ok {
  225. return nil, errRefused
  226. }
  227. services, err = r.Records(ctx, state, false)
  228. if err != nil {
  229. // log.Errorf("Records %s fail: %s", state.Name(), err)
  230. return nil, err
  231. }
  232. return services, nil
  233. }
  234. // Lookup implements the ServiceBackend interface
  235. func (r *SRegionDNS) Lookup(ctx context.Context, state request.Request, name string, typ uint16) (*dns.Msg, error) {
  236. return r.Upstream.Lookup(ctx, state, name, typ)
  237. }
  238. // IsNameError implements the ServiceBackend interface
  239. func (r *SRegionDNS) IsNameError(err error) bool {
  240. return err == errCallNext
  241. }
  242. // Records looks up records in region mysql
  243. func (r *SRegionDNS) Records(ctx context.Context, state request.Request, exact bool) ([]msg.Service, error) {
  244. req := parseRequest(state)
  245. if r.InCloudOnly && !req.srcInCloud {
  246. // deny external request
  247. return nil, errRefused
  248. }
  249. return r.findRecords(req)
  250. }
  251. func (r *SRegionDNS) getHostIpWithName(req *recordRequest) string {
  252. if req.Type() != "A" {
  253. return ""
  254. }
  255. name := req.QueryName()
  256. name = strings.TrimSuffix(name, ".")
  257. ctx := context.Background()
  258. host, _ := models.HostManager.FetchByName(ctx, nil, name)
  259. if host == nil {
  260. return ""
  261. }
  262. ip := host.(*models.SHost).AccessIp
  263. return ip
  264. }
  265. func (r *SRegionDNS) getGuestIpsWithName(req *recordRequest) []string {
  266. ips := []string{}
  267. name := req.QueryName()
  268. projectId := req.ProjectId()
  269. wantOnlyExit := false
  270. if req.Type() == "A" {
  271. ip4s := models.GuestManager.GetIpsInProjectWithName(projectId, name, wantOnlyExit, api.AddressTypeIPv4)
  272. if len(ip4s) > 0 {
  273. ips = append(ips, ip4s...)
  274. }
  275. }
  276. if req.Type() == "AAAA" {
  277. ip6s := models.GuestManager.GetIpsInProjectWithName(projectId, name, wantOnlyExit, api.AddressTypeIPv6)
  278. if len(ip6s) > 0 {
  279. ips = append(ips, ip6s...)
  280. }
  281. }
  282. return ips
  283. }
  284. /*func getK8sServiceBackends(cli *kubernetes.Clientset, req *recordRequest) ([]string, error) {
  285. queryInfo := req.GetK8sQueryInfo()
  286. pods, err := getK8sServicePods(cli, queryInfo.Namespace, queryInfo.ServiceName)
  287. if err != nil {
  288. if k8serrors.IsNotFound(err) {
  289. err = nil
  290. }
  291. return nil, err
  292. }
  293. ips := make([]string, 0)
  294. for _, pod := range pods {
  295. ip := pod.Status.PodIP
  296. if len(ip) != 0 {
  297. ips = append(ips, ip)
  298. }
  299. }
  300. return ips, nil
  301. }*/
  302. /*func (r *SRegionDNS) getK8sClient() (*kubernetes.Clientset, error) {
  303. return r.K8sManager.GetK8sClient()
  304. }*/
  305. /*func getK8sServicePods(cli *kubernetes.Clientset, namespace, name string) ([]v1.Pod, error) {
  306. svc, err := cli.CoreV1().Services(namespace).Get(context.Background(), name, metav1.GetOptions{})
  307. if err != nil {
  308. return nil, err
  309. }
  310. labelSelector := labels.SelectorFromSet(svc.Spec.Selector)
  311. pods, err := cli.CoreV1().Pods(namespace).List(context.Background(), metav1.ListOptions{
  312. LabelSelector: labelSelector.String(),
  313. FieldSelector: fields.Everything().String(),
  314. })
  315. if err != nil {
  316. return nil, err
  317. }
  318. return pods.Items, nil
  319. }*/
  320. func (r *SRegionDNS) Name() string {
  321. return PluginName
  322. }
  323. func (r *SRegionDNS) queryLocalDnsRecords(req *recordRequest) []msg.Service {
  324. var (
  325. projId = req.ProjectId()
  326. getTtl = func(ttl int64) uint32 {
  327. if ttl == 0 {
  328. return defaultTTL
  329. }
  330. return uint32(ttl)
  331. }
  332. )
  333. recs := make([]msg.Service, 0)
  334. records, err := models.DnsRecordManager.QueryDns(projId, req.Name(), req.Type())
  335. if err != nil {
  336. log.Errorf("QueryDns %s %s error: %v", req.Type(), req.Name(), err)
  337. return nil
  338. }
  339. if req.IsSRV() {
  340. for i := range records {
  341. rec := records[i]
  342. // priority weight port host
  343. parts := strings.SplitN(rec.DnsValue, " ", 4)
  344. if len(parts) != 4 {
  345. log.Errorf("Invalid SRV records: %q", rec.DnsValue)
  346. return nil
  347. }
  348. _priority, _weight, _port, host := parts[0], parts[1], parts[2], parts[3]
  349. priority, err := strconv.Atoi(_priority)
  350. if err != nil {
  351. log.Errorf("SRV: invalid priority: %s", _priority)
  352. return nil
  353. }
  354. weight, err := strconv.Atoi(_weight)
  355. if err != nil {
  356. log.Errorf("SRV: invalid weight: %s", _weight)
  357. return nil
  358. }
  359. port, err := strconv.Atoi(_port)
  360. if err != nil {
  361. log.Errorf("SRV: invalid port: %s", _port)
  362. return nil
  363. }
  364. recs = append(recs, msg.Service{
  365. Host: host,
  366. Port: port,
  367. Weight: weight,
  368. Priority: priority,
  369. TTL: getTtl(rec.TTL),
  370. })
  371. }
  372. } else {
  373. for i := range records {
  374. rec := records[i]
  375. recs = append(recs, msg.Service{
  376. Host: rec.DnsValue,
  377. TTL: getTtl(rec.TTL),
  378. })
  379. }
  380. }
  381. return recs
  382. }
  383. func (r *SRegionDNS) isMyDomain(req *recordRequest) bool {
  384. if r.PrimaryZone == "" {
  385. return false
  386. }
  387. qname := req.state.Name()
  388. qnameLabelCount := dns.CountLabel(qname)
  389. if qnameLabelCount <= r.primaryZoneLabelCount {
  390. return false
  391. }
  392. matched := dns.CompareDomainName(r.PrimaryZone, qname)
  393. if matched == r.primaryZoneLabelCount {
  394. return true
  395. }
  396. return false
  397. }
  398. func (r *SRegionDNS) findRecords(req *recordRequest) ([]msg.Service, error) {
  399. // 1. try local dns records table
  400. if !r.isMyDomain(req) {
  401. rrs := r.queryLocalDnsRecords(req)
  402. if len(rrs) > 0 {
  403. return rrs, nil
  404. }
  405. }
  406. isPlainName := req.IsPlainName()
  407. isMyDomain := r.isMyDomain(req)
  408. if isPlainName {
  409. isCloudIp := req.SrcInCloud()
  410. if isCloudIp {
  411. ips := r.findInternalRecordIps(req)
  412. if len(ips) > 0 {
  413. return ips2DnsRecords(ips), nil
  414. } else {
  415. return nil, errNotFound
  416. }
  417. } else {
  418. return nil, errRefused
  419. }
  420. } else if isMyDomain {
  421. ips := r.findInternalRecordIps(req)
  422. if len(ips) > 0 {
  423. return ips2DnsRecords(ips), nil
  424. } else {
  425. return nil, errNotFound
  426. }
  427. } else {
  428. return nil, errCallNext
  429. }
  430. }
  431. func (r *SRegionDNS) findInternalRecordIps(req *recordRequest) []string {
  432. {
  433. // 1. try guest table
  434. ips := r.getGuestIpsWithName(req)
  435. if len(ips) > 0 {
  436. return ips
  437. }
  438. }
  439. {
  440. // 2. try host table
  441. ip := r.getHostIpWithName(req)
  442. if len(ip) > 0 {
  443. return []string{ip}
  444. }
  445. }
  446. /*if !r.K8sSkip {
  447. k8sCli, err := r.getK8sClient()
  448. if err != nil {
  449. log.Warningf("Get k8s client error: %v, skip it.", err)
  450. return nil
  451. }
  452. // 3. try k8s service backends
  453. ips, err := getK8sServiceBackends(k8sCli, req)
  454. if err != nil {
  455. log.Errorf("Get k8s service backends error: %v", err)
  456. }
  457. return ips
  458. }*/
  459. return nil
  460. }
  461. func ips2DnsRecords(ips []string) []msg.Service {
  462. recs := make([]msg.Service, 0)
  463. for _, ip := range ips {
  464. s := msg.Service{Host: ip, TTL: defaultTTL}
  465. recs = append(recs, s)
  466. }
  467. return recs
  468. }