commit
cf1f7e2ee4
9 changed files with 127 additions and 8 deletions
|
@ -56,6 +56,8 @@ Variables names are case sensitives.
|
|||
|Port|int|``6379``|Redis Database port
|
||||
|DB|int|``0``|Redis Database ID
|
||||
|TTL|int|``10``|Redis Time To Live (in seconds)
|
||||
|DNS|Section
|
||||
|XfrIPs|[]string|``*,192.0.2.9,192.0.2.98``|Allowed IPs for XFR transfer (``*`` for any)
|
||||
|
||||
## What is working
|
||||
- Read records (stricts & wildcard) from MySQL
|
||||
|
|
|
@ -1,20 +1,36 @@
|
|||
package core
|
||||
|
||||
import "github.com/miekg/dns"
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/outout14/sacrebleu-dns/utils"
|
||||
)
|
||||
|
||||
//HandleDNSRequest : Handle the DNS request using miekg/dns
|
||||
//Requires dns.ReponseWriter and dns.Msg args
|
||||
func HandleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
|
||||
|
||||
func HandleDNSRequest(w dns.ResponseWriter, r *dns.Msg, conf *utils.Conf) {
|
||||
//dns.Msg object
|
||||
//Will be passed to the parseQuery() function
|
||||
m := new(dns.Msg)
|
||||
m.SetReply(r)
|
||||
m.Compress = false
|
||||
|
||||
if r.Opcode == dns.OpcodeQuery { //Only respond to dns queries
|
||||
ip, _, _ := net.SplitHostPort(w.RemoteAddr().String())
|
||||
|
||||
if r.Question[0].Qtype == dns.TypeAXFR {
|
||||
if utils.XfrAllowed(ip, conf) {
|
||||
parseAXFR(m)
|
||||
} else {
|
||||
m := new(dns.Msg)
|
||||
m.SetRcode(r, dns.RcodeRefused)
|
||||
w.WriteMsg(m)
|
||||
}
|
||||
|
||||
} else if r.Opcode == dns.OpcodeQuery { //Only respond to dns queries
|
||||
parseQuery(m)
|
||||
}
|
||||
|
||||
w.WriteMsg(m) //Write the DNS response
|
||||
|
||||
}
|
||||
|
|
30
core/parseAXFR.go
Normal file
30
core/parseAXFR.go
Normal file
|
@ -0,0 +1,30 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/outout14/sacrebleu-api/api/types"
|
||||
"github.com/outout14/sacrebleu-dns/utils"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func parseAXFR(m *dns.Msg) {
|
||||
log.Infof("DNS : AXFR query for %s\n", m.Question[0].Name) //Log
|
||||
|
||||
records := utils.GetAllRecords(types.Domain{Fqdn: m.Question[0].Name}) //Get the record in the SQL or Redis database
|
||||
|
||||
for _, record := range records {
|
||||
var err error
|
||||
var rr dns.RR
|
||||
rr, err = dns.NewRR(fmt.Sprintf("%s %v %s %s", record.Fqdn, record.TTL, dns.TypeToString[uint16(record.Type)], record.Content)) //Create the response
|
||||
if err == nil { //If no err
|
||||
m.Answer = append(m.Answer, rr) //Append the record to the response
|
||||
} else {
|
||||
logrus.Debug(err)
|
||||
}
|
||||
}
|
||||
logrus.Debug(m.Answer)
|
||||
}
|
|
@ -22,3 +22,7 @@ Port = 6379
|
|||
Password = ""
|
||||
DB = 0
|
||||
TTL = 10 #In seconds
|
||||
|
||||
[DNS]
|
||||
XfrIPs = *, 10.100.0.3 #Array of slaves IPs
|
||||
Nameservers = ns1.example.org, ns2.example.org, ns1.example.com #Arry of NS urls. (the first one is the master)
|
2
go.mod
2
go.mod
|
@ -6,7 +6,7 @@ require (
|
|||
github.com/go-redis/redis/v8 v8.4.2
|
||||
github.com/mattn/go-colorable v0.1.8
|
||||
github.com/miekg/dns v1.1.35
|
||||
github.com/outout14/sacrebleu-api v0.0.0-20201227222118-369cb34174ae
|
||||
github.com/outout14/sacrebleu-api v0.0.0-20210101200542-c74760a559c4
|
||||
github.com/sirupsen/logrus v1.7.0
|
||||
github.com/snowzach/rotatefilehook v0.0.0-20180327172521-2f64f265f58c
|
||||
gopkg.in/ini.v1 v1.62.0
|
||||
|
|
19
go.sum
19
go.sum
|
@ -1,9 +1,13 @@
|
|||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
|
@ -29,13 +33,17 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4
|
|||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonreference v0.19.4/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
|
||||
github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM=
|
||||
github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
|
||||
github.com/go-openapi/spec v0.19.14/go.mod h1:gwrgJS15eCUgjLpMjBJmbZezCsw88LmgeEip0M63doA=
|
||||
github.com/go-openapi/spec v0.20.0 h1:HGLc8AJ7ynOxwv0Lq4TsnwLsWMawHAYiJIFzbcML86I=
|
||||
github.com/go-openapi/spec v0.20.0/go.mod h1:+81FIL1JwC5P3/Iuuozq3pPE9dXdIEGxFutcFKaVbmU=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.11/go.mod h1:Uc0gKkdR+ojzsEpjh39QChyu92vPgIr72POcgHMAgSY=
|
||||
github.com/go-openapi/swag v0.19.12 h1:Bc0bnY2c3AoF7Gc+IMIAQQsD8fLHjHpc19wXvYuayQI=
|
||||
github.com/go-openapi/swag v0.19.12/go.mod h1:eFdyEBkTdoAf/9RXBvj4cr1nH7GD8Kzo5HTt47gr72M=
|
||||
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
|
||||
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||
|
@ -68,8 +76,10 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
|
||||
|
@ -128,6 +138,7 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
|
|||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E=
|
||||
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
|
@ -149,6 +160,7 @@ github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
|
|||
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
|
@ -177,7 +189,10 @@ github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA=
|
|||
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
|
||||
github.com/outout14/sacrebleu-api v0.0.0-20201227222118-369cb34174ae h1:UVlph8jP6sBaURQbzbDglLct9dOV5DeTNHDnHgy5OZ0=
|
||||
github.com/outout14/sacrebleu-api v0.0.0-20201227222118-369cb34174ae/go.mod h1:Wgrhbj7+cZiHa5aufAIyNT3zkwukwtrMPqL/qtZovt4=
|
||||
github.com/outout14/sacrebleu-api v0.0.0-20210101200542-c74760a559c4 h1:OfbKaBi4GOCVerf9y/nDavVrNHj1MjNwGpdOifMR1b0=
|
||||
github.com/outout14/sacrebleu-api v0.0.0-20210101200542-c74760a559c4/go.mod h1:NGAUHRsf0oGAp7nEMU/aXJiehT4fy/AVzG/J9o+gOO4=
|
||||
github.com/outout14/sacrebleu-dns v0.0.4/go.mod h1:0A01AFvUj1K217FJhpvOUV/uRLizwCB5a0vPaB9uVHw=
|
||||
github.com/outout14/sacrebleu-dns v0.0.5/go.mod h1:3wlET68I/Wk+XyUa0gAe6CLFBnBsd5r58YYdXeS+ywI=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
|
@ -214,8 +229,11 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
|
|||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14 h1:PyYN9JH5jY9j6av01SpfRMb+1DWg/i3MbGOKPxJ2wjM=
|
||||
github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E=
|
||||
github.com/swaggo/http-swagger v1.0.0 h1:ksYgVBCYmAaxFsGVGojlPROgYfiQQSllETTWMtHJHTo=
|
||||
github.com/swaggo/http-swagger v1.0.0/go.mod h1:cKIcshBU9yEAnfWv6ZzVKSsEf8h5ozxB8/zHQWyOQ/8=
|
||||
github.com/swaggo/swag v1.7.0 h1:5bCA/MTLQoIqDXXyHfOpMeDvL9j68OY/udlK4pQoo4E=
|
||||
github.com/swaggo/swag v1.7.0/go.mod h1:BdPIL73gvS9NBsdi7M1JOxLvlbfvNRaBP8m6WT6Aajo=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
|
@ -320,6 +338,7 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
|||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20201120155355-20be4ac4bd6e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201208062317-e652b2f42cc7/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e h1:4nW4NLDYnU28ojHaHO8OVxFHk/aQ33U01a9cjED+pzE=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
|
6
main.go
6
main.go
|
@ -35,7 +35,7 @@ func main() {
|
|||
utils.InitLogger(conf)
|
||||
|
||||
//Attach DNS request handler func for all domains
|
||||
dns.HandleFunc(".", core.HandleDNSRequest)
|
||||
dns.HandleFunc(".", func(w dns.ResponseWriter, r *dns.Msg) { core.HandleDNSRequest(w, r, conf) })
|
||||
|
||||
//Initialize the redis database
|
||||
utils.RedisDatabase(conf)
|
||||
|
@ -47,8 +47,10 @@ func main() {
|
|||
}
|
||||
|
||||
//Start the DNS server
|
||||
server := &dns.Server{Addr: conf.App.IP + ":" + strconv.Itoa(conf.App.Port), Net: "udp"} //define the server
|
||||
server := &dns.Server{Addr: conf.App.IP + ":" + strconv.Itoa(conf.App.Port), Net: "tcp"} //define the server
|
||||
logrus.WithFields(logrus.Fields{"ip": conf.App.IP, "port": conf.App.Port}).Infof("SERVER : Started") //log
|
||||
logrus.WithFields(logrus.Fields{"XfrIPs": conf.DNS.XfrIPs}).Debug("")
|
||||
|
||||
err = server.ListenAndServe() //start it
|
||||
utils.CheckErr(err)
|
||||
|
||||
|
|
39
utils/axfr.go
Normal file
39
utils/axfr.go
Normal file
|
@ -0,0 +1,39 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"github.com/outout14/sacrebleu-api/api/types"
|
||||
)
|
||||
|
||||
//XfrAllowed : check if the IP is allowed to perform XFR requests
|
||||
func XfrAllowed(remoteIP string, conf *Conf) bool {
|
||||
for _, ip := range conf.DNS.XfrIPs {
|
||||
if ip == "*" {
|
||||
return true
|
||||
}
|
||||
if ip == remoteIP {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//GetAllRecords : Retrive all records for a domain
|
||||
func GetAllRecords(d types.Domain) []types.Record {
|
||||
results := []types.Record{}
|
||||
|
||||
//Get domain id from his fqdn
|
||||
err := d.GetDomainByFqdn(db)
|
||||
if err != nil {
|
||||
return results
|
||||
}
|
||||
|
||||
results, err = d.GetDomainRecords(db, -1, -1)
|
||||
DbgErr(err)
|
||||
|
||||
soa, err := d.GetSOA(db)
|
||||
DbgErr(err)
|
||||
|
||||
results = append([]types.Record{soa}, results...)
|
||||
|
||||
return results
|
||||
}
|
|
@ -27,10 +27,17 @@ type Redis struct {
|
|||
TTL int `ini:"TTL"`
|
||||
}
|
||||
|
||||
//DNS : Struct for XFR and NS
|
||||
type DNS struct {
|
||||
XfrIPs []string
|
||||
Nameservers []string
|
||||
}
|
||||
|
||||
//Conf : Struct for the whole config.ini file when it will be parsed by go-ini
|
||||
type Conf struct {
|
||||
AppMode string `ini:"app_mode"`
|
||||
App
|
||||
Database
|
||||
Redis
|
||||
DNS DNS
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue