package net
import (
"context"
"errors"
"net/netip"
"syscall"
"unsafe"
"golang.org/x/net/dns/dnsmessage"
)
const cgoAvailable = true
type addrinfoErrno int
func (eai addrinfoErrno ) Error () string { return _C_gai_strerror (_C_int (eai )) }
func (eai addrinfoErrno ) Temporary () bool { return eai == _C_EAI_AGAIN }
func (eai addrinfoErrno ) Timeout () bool { return false }
func (eai addrinfoErrno ) isAddrinfoErrno () {}
func doBlockingWithCtx [T any ](ctx context .Context , blocking func () (T , error )) (T , error ) {
if ctx .Done () == nil {
return blocking ()
}
type result struct {
res T
err error
}
res := make (chan result , 1 )
go func () {
var r result
r .res , r .err = blocking ()
res <- r
}()
select {
case r := <- res :
return r .res , r .err
case <- ctx .Done ():
var zero T
return zero , mapErr (ctx .Err ())
}
}
func cgoLookupHost (ctx context .Context , name string ) (hosts []string , err error ) {
addrs , err := cgoLookupIP (ctx , "ip" , name )
if err != nil {
return nil , err
}
for _ , addr := range addrs {
hosts = append (hosts , addr .String ())
}
return hosts , nil
}
func cgoLookupPort (ctx context .Context , network , service string ) (port int , err error ) {
var hints _C_struct_addrinfo
switch network {
case "" :
case "tcp" , "tcp4" , "tcp6" :
*_C_ai_socktype (&hints ) = _C_SOCK_STREAM
*_C_ai_protocol (&hints ) = _C_IPPROTO_TCP
case "udp" , "udp4" , "udp6" :
*_C_ai_socktype (&hints ) = _C_SOCK_DGRAM
*_C_ai_protocol (&hints ) = _C_IPPROTO_UDP
default :
return 0 , &DNSError {Err : "unknown network" , Name : network + "/" + service }
}
switch ipVersion (network ) {
case '4' :
*_C_ai_family (&hints ) = _C_AF_INET
case '6' :
*_C_ai_family (&hints ) = _C_AF_INET6
}
return doBlockingWithCtx (ctx , func () (int , error ) {
return cgoLookupServicePort (&hints , network , service )
})
}
func cgoLookupServicePort (hints *_C_struct_addrinfo , network , service string ) (port int , err error ) {
cservice , err := syscall .ByteSliceFromString (service )
if err != nil {
return 0 , &DNSError {Err : err .Error(), Name : network + "/" + service }
}
for i , b := range cservice [:len (service )] {
cservice [i ] = lowerASCII (b )
}
var res *_C_struct_addrinfo
gerrno , err := _C_getaddrinfo (nil , (*_C_char )(unsafe .Pointer (&cservice [0 ])), hints , &res )
if gerrno != 0 {
isTemporary := false
switch gerrno {
case _C_EAI_SYSTEM :
if err == nil {
err = syscall .EMFILE
}
default :
err = addrinfoErrno (gerrno )
isTemporary = addrinfoErrno (gerrno ).Temporary ()
}
return 0 , &DNSError {Err : err .Error(), Name : network + "/" + service , IsTemporary : isTemporary }
}
defer _C_freeaddrinfo (res )
for r := res ; r != nil ; r = *_C_ai_next (r ) {
switch *_C_ai_family (r ) {
case _C_AF_INET :
sa := (*syscall .RawSockaddrInet4 )(unsafe .Pointer (*_C_ai_addr (r )))
p := (*[2 ]byte )(unsafe .Pointer (&sa .Port ))
return int (p [0 ])<<8 | int (p [1 ]), nil
case _C_AF_INET6 :
sa := (*syscall .RawSockaddrInet6 )(unsafe .Pointer (*_C_ai_addr (r )))
p := (*[2 ]byte )(unsafe .Pointer (&sa .Port ))
return int (p [0 ])<<8 | int (p [1 ]), nil
}
}
return 0 , &DNSError {Err : "unknown port" , Name : network + "/" + service }
}
func cgoLookupHostIP (network , name string ) (addrs []IPAddr , err error ) {
acquireThread ()
defer releaseThread ()
var hints _C_struct_addrinfo
*_C_ai_flags (&hints ) = cgoAddrInfoFlags
*_C_ai_socktype (&hints ) = _C_SOCK_STREAM
*_C_ai_family (&hints ) = _C_AF_UNSPEC
switch ipVersion (network ) {
case '4' :
*_C_ai_family (&hints ) = _C_AF_INET
case '6' :
*_C_ai_family (&hints ) = _C_AF_INET6
}
h , err := syscall .BytePtrFromString (name )
if err != nil {
return nil , &DNSError {Err : err .Error(), Name : name }
}
var res *_C_struct_addrinfo
gerrno , err := _C_getaddrinfo ((*_C_char )(unsafe .Pointer (h )), nil , &hints , &res )
if gerrno != 0 {
isErrorNoSuchHost := false
isTemporary := false
switch gerrno {
case _C_EAI_SYSTEM :
if err == nil {
err = syscall .EMFILE
}
case _C_EAI_NONAME , _C_EAI_NODATA :
err = errNoSuchHost
isErrorNoSuchHost = true
default :
err = addrinfoErrno (gerrno )
isTemporary = addrinfoErrno (gerrno ).Temporary ()
}
return nil , &DNSError {Err : err .Error(), Name : name , IsNotFound : isErrorNoSuchHost , IsTemporary : isTemporary }
}
defer _C_freeaddrinfo (res )
for r := res ; r != nil ; r = *_C_ai_next (r ) {
if *_C_ai_socktype (r ) != _C_SOCK_STREAM {
continue
}
switch *_C_ai_family (r ) {
case _C_AF_INET :
sa := (*syscall .RawSockaddrInet4 )(unsafe .Pointer (*_C_ai_addr (r )))
addr := IPAddr {IP : copyIP (sa .Addr [:])}
addrs = append (addrs , addr )
case _C_AF_INET6 :
sa := (*syscall .RawSockaddrInet6 )(unsafe .Pointer (*_C_ai_addr (r )))
addr := IPAddr {IP : copyIP (sa .Addr [:]), Zone : zoneCache .name (int (sa .Scope_id ))}
addrs = append (addrs , addr )
}
}
return addrs , nil
}
func cgoLookupIP (ctx context .Context , network , name string ) (addrs []IPAddr , err error ) {
return doBlockingWithCtx (ctx , func () ([]IPAddr , error ) {
return cgoLookupHostIP (network , name )
})
}
const (
nameinfoLen = 64
maxNameinfoLen = 4096
)
func cgoLookupPTR (ctx context .Context , addr string ) (names []string , err error ) {
ip , err := netip .ParseAddr (addr )
if err != nil {
return nil , &DNSError {Err : "invalid address" , Name : addr }
}
sa , salen := cgoSockaddr (IP (ip .AsSlice ()), ip .Zone ())
if sa == nil {
return nil , &DNSError {Err : "invalid address " + ip .String (), Name : addr }
}
return doBlockingWithCtx (ctx , func () ([]string , error ) {
return cgoLookupAddrPTR (addr , sa , salen )
})
}
func cgoLookupAddrPTR (addr string , sa *_C_struct_sockaddr , salen _C_socklen_t ) (names []string , err error ) {
acquireThread ()
defer releaseThread ()
var gerrno int
var b []byte
for l := nameinfoLen ; l <= maxNameinfoLen ; l *= 2 {
b = make ([]byte , l )
gerrno , err = cgoNameinfoPTR (b , sa , salen )
if gerrno == 0 || gerrno != _C_EAI_OVERFLOW {
break
}
}
if gerrno != 0 {
isErrorNoSuchHost := false
isTemporary := false
switch gerrno {
case _C_EAI_SYSTEM :
if err == nil {
err = syscall .EMFILE
}
case _C_EAI_NONAME :
err = errNoSuchHost
isErrorNoSuchHost = true
default :
err = addrinfoErrno (gerrno )
isTemporary = addrinfoErrno (gerrno ).Temporary ()
}
return nil , &DNSError {Err : err .Error(), Name : addr , IsTemporary : isTemporary , IsNotFound : isErrorNoSuchHost }
}
for i := 0 ; i < len (b ); i ++ {
if b [i ] == 0 {
b = b [:i ]
break
}
}
return []string {absDomainName (string (b ))}, nil
}
func cgoSockaddr (ip IP , zone string ) (*_C_struct_sockaddr , _C_socklen_t ) {
if ip4 := ip .To4 (); ip4 != nil {
return cgoSockaddrInet4 (ip4 ), _C_socklen_t (syscall .SizeofSockaddrInet4 )
}
if ip6 := ip .To16 (); ip6 != nil {
return cgoSockaddrInet6 (ip6 , zoneCache .index (zone )), _C_socklen_t (syscall .SizeofSockaddrInet6 )
}
return nil , 0
}
func cgoLookupCNAME (ctx context .Context , name string ) (cname string , err error , completed bool ) {
resources , err := resSearch (ctx , name , int (dnsmessage .TypeCNAME ), int (dnsmessage .ClassINET ))
if err != nil {
return
}
cname , err = parseCNAMEFromResources (resources )
if err != nil {
return "" , err , false
}
return cname , nil , true
}
func resSearch (ctx context .Context , hostname string , rtype , class int ) ([]dnsmessage .Resource , error ) {
return doBlockingWithCtx (ctx , func () ([]dnsmessage .Resource , error ) {
return cgoResSearch (hostname , rtype , class )
})
}
func cgoResSearch (hostname string , rtype , class int ) ([]dnsmessage .Resource , error ) {
acquireThread ()
defer releaseThread ()
state := (*_C_struct___res_state )(_C_malloc (unsafe .Sizeof (_C_struct___res_state {})))
defer _C_free (unsafe .Pointer (state ))
if err := _C_res_ninit (state ); err != nil {
return nil , errors .New ("res_ninit failure: " + err .Error())
}
defer _C_res_nclose (state )
bufSize := maxDNSPacketSize
buf := (*_C_uchar )(_C_malloc (uintptr (bufSize )))
defer _C_free (unsafe .Pointer (buf ))
s , err := syscall .BytePtrFromString (hostname )
if err != nil {
return nil , err
}
var size int
for {
size , _ = _C_res_nsearch (state , (*_C_char )(unsafe .Pointer (s )), class , rtype , buf , bufSize )
if size <= 0 || size > 0xffff {
return nil , errors .New ("res_nsearch failure" )
}
if size <= bufSize {
break
}
_C_free (unsafe .Pointer (buf ))
bufSize = size
buf = (*_C_uchar )(_C_malloc (uintptr (bufSize )))
}
var p dnsmessage .Parser
if _ , err := p .Start (unsafe .Slice ((*byte )(unsafe .Pointer (buf )), size )); err != nil {
return nil , err
}
p .SkipAllQuestions ()
resources , err := p .AllAnswers ()
if err != nil {
return nil , err
}
return resources , nil
}
The pages are generated with Golds v0.6.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds .