master #2
4 changed files with 133 additions and 126 deletions
146
awl.go
146
awl.go
|
@ -1,32 +1,21 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/c-robinson/iplib"
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/urfave/cli/v2"
|
||||
"golang.org/x/net/idna"
|
||||
)
|
||||
|
||||
// The basic structure of a DNS request
|
||||
type request struct {
|
||||
server string // The server to make the DNS request from
|
||||
request uint16 // The type of request
|
||||
name string // The domain name to make a DNS request for
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Custom version string
|
||||
cli.VersionPrinter = func(c *cli.Context) {
|
||||
|
@ -126,8 +115,8 @@ func main() {
|
|||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
var err error
|
||||
|
||||
req := parseArgs(c.Args().Slice())
|
||||
var resp util.Response
|
||||
resp.Answers = parseArgs(c.Args().Slice())
|
||||
// Set DNS-over-TLS, if enabled
|
||||
port := c.Int("port")
|
||||
|
||||
|
@ -141,29 +130,26 @@ func main() {
|
|||
}
|
||||
|
||||
if !c.Bool("https") {
|
||||
req.server = net.JoinHostPort(req.server, strconv.Itoa(port))
|
||||
resp.Answers.Server = net.JoinHostPort(resp.Answers.Server, strconv.Itoa(port))
|
||||
} else {
|
||||
req.server = "https://" + req.server
|
||||
resp.Answers.Server = "https://" + resp.Answers.Server
|
||||
}
|
||||
|
||||
// Process the IP/Phone number so a PTR/NAPTR can be done
|
||||
if c.Bool("reverse") {
|
||||
if dns.TypeToString[req.request] == "A" {
|
||||
req.request = dns.StringToType["PTR"]
|
||||
}
|
||||
req.name, err = reverseDNS(req.name, dns.TypeToString[req.request])
|
||||
if err != nil {
|
||||
return err
|
||||
if dns.TypeToString[resp.Answers.Request] == "A" {
|
||||
resp.Answers.Request = dns.StringToType["PTR"]
|
||||
}
|
||||
resp.Answers.Name, err = util.ReverseDNS(resp.Answers.Name, dns.TypeToString[resp.Answers.Request])
|
||||
}
|
||||
|
||||
// if the domain is not canonical, make it canonical
|
||||
if !strings.HasSuffix(req.name, ".") {
|
||||
req.name = fmt.Sprintf("%s.", req.name)
|
||||
if !strings.HasSuffix(resp.Answers.Name, ".") {
|
||||
resp.Answers.Name = fmt.Sprintf("%s.", resp.Answers.Name)
|
||||
}
|
||||
|
||||
msg := new(dns.Msg)
|
||||
msg.SetQuestion(req.name, req.request)
|
||||
msg.SetQuestion(resp.Answers.Name, resp.Answers.Request)
|
||||
|
||||
// Set DNSSEC if requested
|
||||
if c.Bool("dnssec") {
|
||||
|
@ -171,13 +157,12 @@ func main() {
|
|||
}
|
||||
|
||||
var (
|
||||
in *dns.Msg
|
||||
rtt time.Duration
|
||||
in *dns.Msg
|
||||
)
|
||||
|
||||
// Make the DNS request
|
||||
if c.Bool("https") {
|
||||
in, err = resolveHTTPS(msg, req.server)
|
||||
in, err = util.ResolveHTTPS(msg, resp.Answers.Server)
|
||||
} else if c.Bool("quic") {
|
||||
return fmt.Errorf("quic: not yet implemented")
|
||||
} else {
|
||||
|
@ -208,7 +193,7 @@ func main() {
|
|||
if c.Bool("tls") {
|
||||
d.Net = "tcp-tls"
|
||||
}
|
||||
in, rtt, err = d.Exchange(msg, req.server)
|
||||
in, resp.Answers.RTT, err = d.Exchange(msg, resp.Answers.Server)
|
||||
|
||||
// If UDP truncates, use TCP instead
|
||||
if !c.Bool("no-truncate") {
|
||||
|
@ -221,7 +206,7 @@ func main() {
|
|||
if c.Bool("6") {
|
||||
d.Net = "tcp6"
|
||||
}
|
||||
in, rtt, err = d.Exchange(msg, req.server)
|
||||
in, resp.Answers.RTT, err = d.Exchange(msg, resp.Answers.Server)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -231,14 +216,14 @@ func main() {
|
|||
}
|
||||
|
||||
if c.Bool("json") {
|
||||
json, _ := json.Marshal(in)
|
||||
json, _ := json.MarshalIndent(in, "", " ")
|
||||
fmt.Println(string(json))
|
||||
} else {
|
||||
if !c.Bool("short") {
|
||||
// Print everything
|
||||
fmt.Println(in)
|
||||
fmt.Println(";; Query time:", rtt)
|
||||
fmt.Println(";; SERVER:", req.server)
|
||||
fmt.Println(";; Query time:", resp.Answers.RTT)
|
||||
fmt.Println(";; SERVER:", resp.Answers.Server)
|
||||
fmt.Println(";; WHEN:", time.Now().Format(time.RFC1123))
|
||||
fmt.Println(";; MSG SIZE rcvd:", in.Len())
|
||||
} else {
|
||||
|
@ -261,114 +246,51 @@ func main() {
|
|||
}
|
||||
}
|
||||
|
||||
func parseArgs(args []string) request {
|
||||
func parseArgs(args []string) util.Answers {
|
||||
var (
|
||||
server string
|
||||
req uint16
|
||||
name string
|
||||
resp util.Response
|
||||
)
|
||||
for _, arg := range args {
|
||||
// If it starts with @, it's a DNS server
|
||||
if strings.HasPrefix(arg, "@") {
|
||||
server = strings.Split(arg, "@")[1]
|
||||
resp.Answers.Server = strings.Split(arg, "@")[1]
|
||||
continue
|
||||
}
|
||||
// If there's a dot, it's a name
|
||||
if strings.Contains(arg, ".") {
|
||||
name, _ = idna.ToUnicode(arg)
|
||||
resp.Answers.Name, _ = idna.ToUnicode(arg)
|
||||
continue
|
||||
}
|
||||
// If it's a request, it's a request (duh)
|
||||
if r, ok := dns.StringToType[strings.ToUpper(arg)]; ok {
|
||||
req = r
|
||||
resp.Answers.Request = r
|
||||
continue
|
||||
}
|
||||
|
||||
//else, assume it's a name
|
||||
name, _ = idna.ToUnicode(arg)
|
||||
resp.Answers.Name, _ = idna.ToUnicode(arg)
|
||||
}
|
||||
|
||||
// If nothing was set, set a default
|
||||
if name == "" {
|
||||
name = "."
|
||||
if req == 0 {
|
||||
req = dns.StringToType["NS"]
|
||||
if resp.Answers.Name == "" {
|
||||
resp.Answers.Name = "."
|
||||
if resp.Answers.Request == 0 {
|
||||
resp.Answers.Request = dns.StringToType["NS"]
|
||||
}
|
||||
} else {
|
||||
if req == 0 {
|
||||
req = dns.StringToType["A"]
|
||||
if resp.Answers.Request == 0 {
|
||||
resp.Answers.Request = dns.StringToType["A"]
|
||||
}
|
||||
}
|
||||
if server == "" {
|
||||
if resp.Answers.Server == "" {
|
||||
resolv, err := dns.ClientConfigFromFile("/etc/resolv.conf")
|
||||
if err != nil { // Query Google by default, needed for Windows since the DNS library doesn't support Windows
|
||||
// TODO: Actually find where windows stuffs its dns resolvers
|
||||
server = "8.8.4.4"
|
||||
resp.Answers.Server = "8.8.4.4"
|
||||
} else {
|
||||
server = resolv.Servers[0]
|
||||
resp.Answers.Server = resolv.Servers[0]
|
||||
}
|
||||
}
|
||||
|
||||
return request{server: server, request: req, name: name}
|
||||
}
|
||||
|
||||
func reverseDNS(dom string, q string) (string, error) {
|
||||
if q == "PTR" {
|
||||
if strings.Contains(dom, ".") {
|
||||
// It's an IPv4 address
|
||||
ip := net.ParseIP(dom)
|
||||
if ip != nil {
|
||||
return iplib.IP4ToARPA(ip), nil
|
||||
} else {
|
||||
return "", errors.New("error: Could not parse IPv4 address")
|
||||
}
|
||||
|
||||
} else if strings.Contains(dom, ":") {
|
||||
// It's an IPv6 address
|
||||
ip := net.ParseIP(dom)
|
||||
if ip != nil {
|
||||
return iplib.IP6ToARPA(ip), nil
|
||||
} else {
|
||||
return "", errors.New("error: Could not parse IPv6 address")
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", errors.New("error: -x flag given but no IP found")
|
||||
}
|
||||
|
||||
func resolveHTTPS(msg *dns.Msg, server string) (*dns.Msg, error) {
|
||||
httpR := &http.Client{}
|
||||
buf, err := msg.Pack()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
query := server + "?dns=" + base64.RawURLEncoding.EncodeToString(buf)
|
||||
req, err := http.NewRequest("GET", query, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Accept", "application/dns-message")
|
||||
|
||||
res, err := httpR.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("bad HTTP Request: %d", res.StatusCode)
|
||||
}
|
||||
|
||||
fullRes, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response := dns.Msg{}
|
||||
err = response.Unpack(fullRes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &response, nil
|
||||
|
||||
return util.Answers{Server: resp.Answers.Server, Request: resp.Answers.Request, Name: resp.Answers.Name}
|
||||
}
|
||||
|
|
9
go.mod
9
go.mod
|
@ -1,4 +1,4 @@
|
|||
module example.com/awl
|
||||
module git.froth.zone/sam/awl
|
||||
|
||||
go 1.18
|
||||
|
||||
|
@ -13,8 +13,9 @@ require (
|
|||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c // indirect
|
||||
golang.org/x/mod v0.4.2 // indirect
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/tools v0.1.11 // indirect
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
)
|
||||
|
|
15
go.sum
15
go.sum
|
@ -2,8 +2,6 @@ github.com/c-robinson/iplib v1.0.3 h1:NG0UF0GoEsrC1/vyfX1Lx2Ss7CySWl3KqqXh3q4DdP
|
|||
github.com/c-robinson/iplib v1.0.3/go.mod h1:i3LuuFL1hRT5gFpBRnEydzw8R6yhGkF4szNDIbF8pgo=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/miekg/dns v1.1.49 h1:qe0mQU3Z/XpFeE+AEBo2rqaS1IPBJ3anmqZ4XiZJVG8=
|
||||
github.com/miekg/dns v1.1.49/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
||||
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
|
@ -15,15 +13,12 @@ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsr
|
|||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 h1:Yqz/iviulwKwAREEeUd3nbBFn0XuyJqkoft2IlrvOhc=
|
||||
golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220621193019-9d032be2e588 h1:9ubFuySsnAJYGyJrZ3koiEv8FyqofCBdz3G9Mbf2YFc=
|
||||
golang.org/x/net v0.0.0-20220621193019-9d032be2e588/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -36,8 +31,8 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c h1:aFV+BgZ4svzjfabn8ERpuB4JI4N6/rdy1iusx77G3oU=
|
||||
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
|
@ -46,9 +41,9 @@ golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
|||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 h1:BonxutuHCTL0rBDnZlKjpGIQFTjyUVTexFOdWkB6Fg0=
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.11 h1:loJ25fNOEhSXfHrpoGj91eCUThwdNX6u24rO1xnNteY=
|
||||
golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
|
89
util/dns.go
Normal file
89
util/dns.go
Normal file
|
@ -0,0 +1,89 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/c-robinson/iplib"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
Answers Answers `json:"Response"` //
|
||||
}
|
||||
|
||||
// The Answers struct is the basic structure of a DNS request
|
||||
// to be returned to the user upon making a request
|
||||
type Answers struct {
|
||||
Server string `json:"Server"` // The server to make the DNS request from
|
||||
Request uint16 `json:"Request"` // The type of request
|
||||
Name string `json:"Name"` // The domain name to make a DNS request for
|
||||
RTT time.Duration `json:"RTT"` // When AWL was ran
|
||||
}
|
||||
|
||||
func ReverseDNS(dom string, q string) (string, error) {
|
||||
if q == "PTR" {
|
||||
if strings.Contains(dom, ".") {
|
||||
// It's an IPv4 address
|
||||
ip := net.ParseIP(dom)
|
||||
if ip != nil {
|
||||
return iplib.IP4ToARPA(ip), nil
|
||||
} else {
|
||||
return "", errors.New("error: Could not parse IPv4 address")
|
||||
}
|
||||
|
||||
} else if strings.Contains(dom, ":") {
|
||||
// It's an IPv6 address
|
||||
ip := net.ParseIP(dom)
|
||||
if ip != nil {
|
||||
return iplib.IP6ToARPA(ip), nil
|
||||
} else {
|
||||
return "", errors.New("error: Could not parse IPv6 address")
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", errors.New("error: -x flag given but no IP found")
|
||||
}
|
||||
|
||||
func ResolveHTTPS(msg *dns.Msg, server string) (*dns.Msg, error) {
|
||||
httpR := &http.Client{}
|
||||
buf, err := msg.Pack()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
query := server + "?dns=" + base64.RawURLEncoding.EncodeToString(buf)
|
||||
req, err := http.NewRequest("GET", query, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Accept", "application/dns-message")
|
||||
|
||||
res, err := httpR.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("bad HTTP Request: %d", res.StatusCode)
|
||||
}
|
||||
|
||||
fullRes, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response := dns.Msg{}
|
||||
err = response.Unpack(fullRes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &response, nil
|
||||
|
||||
}
|
Loading…
Reference in a new issue