pax_global_header00006660000000000000000000000064140532233630014513gustar00rootroot0000000000000052 comment=263a1998d7aa71822a87a427cefbec66d1e198b6 go-systemd-5.1.0/000077500000000000000000000000001405322336300136115ustar00rootroot00000000000000go-systemd-5.1.0/.gitignore000066400000000000000000000000211405322336300155720ustar00rootroot00000000000000# VSCode .vscode go-systemd-5.1.0/LICENSE000066400000000000000000000020601405322336300146140ustar00rootroot00000000000000MIT License Copyright (c) 2020 Iguane Solutions Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.go-systemd-5.1.0/README.md000066400000000000000000000163641405322336300151020ustar00rootroot00000000000000# go-systemd [![Go Report Card](https://goreportcard.com/badge/github.com/iguanesolutions/go-systemd)](https://goreportcard.com/report/github.com/iguanesolutions/go-systemd) [![PkgGoDev](https://pkg.go.dev/badge/github.com/iguanesolutions/go-systemd/v5)](https://pkg.go.dev/github.com/iguanesolutions/go-systemd/v5) Easily communicate with systemd when run as daemon within a service unit. ## Notifier [![PkgGoDev](https://pkg.go.dev/badge/github.com/iguanesolutions/go-systemd/v5/notify)](https://pkg.go.dev/github.com/iguanesolutions/go-systemd/v5/notify) With notifier you can notify to systemd that your program is starting, stopping, reloading... For example, if your daemon needs some time for initializing its controllers before really being considered as ready, you can specify to systemd that this is a "notify" service and send it a notification when ready. It is safe to use it even if systemd notify support is disabled (noop call). ```systemdunit [Service] Type=notify ``` ```go import ( sysdnotify "github.com/iguanesolutions/go-systemd/v5/notify" ) // Init http server server := &http.Server{ Addr: "host:port", Handler: myHTTPHandler, } /* Do some more inits */ // Notify ready to systemd if err = sysdnotify.Ready(); err != nil { log.Printf("failed to notify ready to systemd: %v\n", err) } // Start the server if err = server.ListenAndServe(); err != nil { log.Printf("failed to start http server: %v\n", err) } ``` When stopping, you can notify systemd that you have indeed received the SIGTERM and you have launched the stop procedure ```go import ( sysdnotify "github.com/iguanesolutions/go-systemd/v5/notify" ) // Notify to systemd that we are stopping var err error if err = sysdnotify.Stopping(); err != nil { log.Printf("failed to notify stopping to systemd: %v\n", err) } /* Stop others things */ // Stop the server (with timeout) ctx, cancelCtx := context.WithTimeout(context.Background(), 5*time.Second) defer cancelCtx() if err = server.Shutdown(ctx); err != nil { log.Printf("failed to shutdown http server: %v\n", err) } ``` You can also notify status to systemd ```go import ( sysdnotify "github.com/iguanesolutions/go-systemd/v5/notify" ) if err := sysdnotify.Status(fmt.Sprintf("There is currently %d active connections", activeConns)); err != nil { log.Printf("failed to notify status to systemd: %v\n", err) } ``` systemctl status output example: ```systemctlstatus user@host:~$ systemctl status superapp.service ● superapp.service - superapp Loaded: loaded (/lib/systemd/system/superapp.service; enabled) Active: active (running) since Mon 2018-06-25 08:54:35 UTC; 3 days ago Main PID: 2604 (superapp) Status: "There is currently 1506 active connections" ... ``` ### Watchdog [![PkgGoDev](https://pkg.go.dev/badge/github.com/iguanesolutions/go-systemd/v5/notify/watchdog)](https://pkg.go.dev/github.com/iguanesolutions/go-systemd/v5/notify/watchdog) ```systemdunit [Service] Type=notify WatchdogSec=30s ``` ```go import ( sysdwatchdog "github.com/iguanesolutions/go-systemd/v5/notify/watchdog" ) // Init systemd watchdog, same as the notifier, it can be nil if your os does not support it watchdog, err := sysdwatchdog.New() if err != nil { log.Printf("failed to initialize systemd watchdog controller: %v\n", err) } if watchdog != nil { // Then start a watcher worker go func() { ticker := watchdog.NewTicker() defer ticker.Stop() for { select { // Ticker chan case <-ticker.C: // Check if something wrong, if not send heartbeat if allGood { if err = watchdog.SendHeartbeat(); err != nil { log.Printf("failed to send systemd watchdog heartbeat: %v\n", err) } } // Some stop signal chan case <-stopSig: return } } }() } ``` ## Resolved [![PkgGoDev](https://pkg.go.dev/badge/github.com/iguanesolutions/go-systemd/resolved/resolved)](https://pkg.go.dev/github.com/iguanesolutions/go-systemd/v5/resolved) This package is still under development and very experimental, do not use it in production. We started this package in order to go deep into the DNS world. So we are opened to any suggestions/contributions on this. DNS is not trivial at all so there can be some stuff that are not rfc compliant (like sorting addresses etc...). The resolved package features: * Pure Go implementation of `org.freedesktop.resolve1` dbus interface * Resolver type (which uses the underlying dbus interface) that tries to implement the same methods as `net.Resolver` from Go standard library * Unit tests (make sure Go resolver and systemd-resolved query the same dns server) ### Dbus The following example shows how to use the resolve1 dbus connection to resolve an host: ```go package main import ( "context" "fmt" "log" "syscall" "github.com/iguanesolutions/go-systemd/v5/resolved" ) func main() { c, err := resolved.NewConn() if err != nil { log.Fatal("ERROR: ", err) } ctx := context.Background() addrs, canonical, flags, err := c.ResolveHostname(ctx, 0, "google.com", syscall.AF_UNSPEC, 0) if err != nil { log.Println("ERROR: ", err) } else { fmt.Println("Addresses: ", addrs) fmt.Println("Canonical: ", canonical) fmt.Println("OutputFlags: ", flags) } err = c.Close() if err != nil { log.Println("ERROR: ", err) } } ``` Output: ```output Addresses: [{ IfIndex: 2, Family: 2, IP: 142.250.74.238, } { IfIndex: 2, Family: 10, IP: 2a00:1450:4007:80b::200e, }] Canonical: google.com Flags: 1 ``` ### Resolver The following example shows how to use the resolved Resolver to resolve an host: ```go package main import ( "context" "fmt" "log" "github.com/iguanesolutions/go-systemd/v5/resolved" ) func main() { r, err := resolved.NewResolver() if err != nil { log.Fatal("ERROR: ", err) } ctx := context.Background() addrs, err := r.LookupHost(ctx, "google.com") if err != nil { log.Println("ERROR: ", err) } else { fmt.Println("Addresses: ", addrs) } err = r.Close() if err != nil { log.Println("ERROR: ", err) } } ``` Output: ```output Addresses: [2a00:1450:4007:80b::200e 142.250.74.238] ``` ### HTTP Client The following example shows how to use the systemd-resolved Resolver with the Go http client from the standard library: ```go package main import ( "fmt" "log" "net/http" "github.com/iguanesolutions/go-systemd/v5/resolved" ) func main() { r, err := resolved.NewResolver() if err != nil { log.Fatal("ERROR: ", err) } // if you want to make a custom http client using systemd-resolved as resolver httpCli := &http.Client{ Transport: &http.Transport{ DialContext: r.DialContext, }, } // or if you don't have an http client you can call HTTPClient method on resolver // it comes with some nice default values. httpCli = r.HTTPClient() resp, err := httpCli.Get("https://google.com") if err != nil { log.Println("ERROR: ", err) } else { fmt.Println("Status: ", resp.Status) err = resp.Body.Close() if err != nil { log.Println("ERROR: ", err) } } err = r.Close() if err != nil { log.Println("ERROR: ", err) } } ``` Output: ```output Status: 200 OK ``` go-systemd-5.1.0/go.mod000066400000000000000000000002731405322336300147210ustar00rootroot00000000000000module github.com/iguanesolutions/go-systemd/v5 go 1.13 require ( github.com/godbus/dbus/v5 v5.0.4 github.com/miekg/dns v1.1.42 golang.org/x/net v0.0.0-20210525063256-abc453219eb5 ) go-systemd-5.1.0/go.sum000066400000000000000000000032741405322336300147520ustar00rootroot00000000000000github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/miekg/dns v1.1.42 h1:gWGe42RGaIqXQZ+r3WUGEKBEtvPHY2SXo4dqixDNxuY= github.com/miekg/dns v1.1.42/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210525063256-abc453219eb5 h1:wjuX4b5yYQnEQHzd+CBcrcC6OVR2J1CN6mUy0oSxIPo= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= go-systemd-5.1.0/journald/000077500000000000000000000000001405322336300154275ustar00rootroot00000000000000go-systemd-5.1.0/journald/prefixes.go000066400000000000000000000013771405322336300176130ustar00rootroot00000000000000package sysdjournald const ( // EmergPrefix is the string to prefix in Emergency for systemd-journald EmergPrefix = "<0>" // AlertPrefix is the string to prefix in Alert for systemd-journald AlertPrefix = "<1>" // CritPrefix is the string to prefix in Critical for systemd-journald CritPrefix = "<2>" // ErrPrefix is the string to prefix in Error for systemd-journald ErrPrefix = "<3>" // WarningPrefix is the string to prefix in Warning for systemd-journald WarningPrefix = "<4>" // NoticePrefix is the string to prefix in Notice for systemd-journald NoticePrefix = "<5>" // InfoPrefix is the string to prefix in Info for systemd-journald InfoPrefix = "<6>" // DebugPrefix is the string to prefix in Debug for systemd-journald DebugPrefix = "<7>" ) go-systemd-5.1.0/notify/000077500000000000000000000000001405322336300151215ustar00rootroot00000000000000go-systemd-5.1.0/notify/notify.go000066400000000000000000000034111405322336300167570ustar00rootroot00000000000000package sysdnotify import ( "fmt" "net" ) var socket *net.UnixAddr // IsEnabled tells if systemd notify socket has been detected or not. func IsEnabled() bool { return socket != nil } // Ready sends systemd notify READY=1 func Ready() error { return Send("READY=1") } // Reloading sends systemd notify RELOADING=1 func Reloading() error { return Send("RELOADING=1") } // Stopping sends systemd notify STOPPING=1 func Stopping() error { return Send("STOPPING=1") } // Status sends systemd notify STATUS=%s{status} func Status(status string) error { return Send(fmt.Sprintf("STATUS=%s", status)) } // ErrNo sends systemd notify ERRNO=%d{errno} func ErrNo(errno int) error { return Send(fmt.Sprintf("ERRNO=%d", errno)) } // BusError sends systemd notify BUSERROR=%s{buserror} func BusError(buserror string) error { return Send(fmt.Sprintf("BUSERROR=%s", buserror)) } // MainPID sends systemd notify MAINPID=%d{mainpid} func MainPID(mainpid int) error { return Send(fmt.Sprintf("MAINPID=%d", mainpid)) } // WatchDog sends systemd notify WATCHDOG=1 func WatchDog() error { return Send("WATCHDOG=1") } // WatchDogUSec sends systemd notify WATCHDOG_USEC=%d{µsec} func WatchDogUSec(usec int64) error { return Send(fmt.Sprintf("WATCHDOG_USEC=%d", usec)) } // Send state thru the notify socket if any. // If the notify socket was not detected, it is a noop call. // Use IsEnabled() to determine if the notify socket has been detected. func Send(state string) error { if socket == nil { return nil } conn, err := net.DialUnix(socket.Net, nil, socket) if err != nil { return fmt.Errorf("can't open unix socket: %v", err) } defer conn.Close() if _, err = conn.Write([]byte(state)); err != nil { return fmt.Errorf("can't write into the unix socket: %v", err) } return nil } go-systemd-5.1.0/notify/notify_init.go000066400000000000000000000003541405322336300200050ustar00rootroot00000000000000// +build linux package sysdnotify import ( "net" "os" ) func init() { if notifySocketName := os.Getenv("NOTIFY_SOCKET"); notifySocketName != "" { socket = &net.UnixAddr{ Name: notifySocketName, Net: "unixgram", } } } go-systemd-5.1.0/notify/watchdog/000077500000000000000000000000001405322336300167215ustar00rootroot00000000000000go-systemd-5.1.0/notify/watchdog/watchdog.go000066400000000000000000000047401405322336300210550ustar00rootroot00000000000000package sysdwatchdog import ( "errors" "fmt" "os" "strconv" "time" sysdnotify "github.com/iguanesolutions/go-systemd/v5/notify" ) // WatchDog is an interface to the systemd watchdog mechanism type WatchDog struct { interval time.Duration checks time.Duration } // New returns an initialized and ready to use WatchDog func New() (wd *WatchDog, err error) { // Check WatchDog is supported and usable interval, err := getWatchDogInterval() if err != nil { return } // Return the initialized controller wd = &WatchDog{ interval: interval, checks: interval / 2, } return } // based on https://github.com/coreos/go-systemd/blob/master/daemon/watchdog.go func getWatchDogInterval() (interval time.Duration, err error) { // WATCHDOG_USEC wusec := os.Getenv("WATCHDOG_USEC") if wusec == "" { err = errors.New("watchdog does not seem to be enabled: WATCHDOG_USEC env unset") return } wusecTyped, err := strconv.Atoi(wusec) if err != nil { err = fmt.Errorf("can't convert WATCHDOG_USEC as int: %s", err) return } if wusecTyped <= 0 { err = fmt.Errorf("WATCHDOG_USEC must be a positive number") return } interval = time.Duration(wusecTyped) * time.Microsecond // WATCHDOG_PID wpid := os.Getenv("WATCHDOG_PID") if wpid == "" { return // No WATCHDOG_PID: can't check if we are the one, let's go with it } wpidTyped, err := strconv.Atoi(wpid) if err != nil { err = fmt.Errorf("can't convert WATCHDOG_PID as int: %s", err) return } if os.Getpid() != wpidTyped { err = fmt.Errorf("WATCHDOG_PID is %d and we are %d: we are not the watched PID", wpidTyped, os.Getpid()) } return } // SendHeartbeat sends a keepalive notification to systemd watchdog func (c *WatchDog) SendHeartbeat() error { if !sysdnotify.IsEnabled() { return errors.New("failed to notify watchdog: systemd notify is diabled") } return sysdnotify.WatchDog() } // GetChecksDuration returns the ideal time for a client to perform (active or passive collect) checks. // Is is equal at 1/3 of watchdogInterval func (c *WatchDog) GetChecksDuration() time.Duration { return c.checks } // GetLimitDuration returns the systemd watchdog limit provided by systemd func (c *WatchDog) GetLimitDuration() time.Duration { return c.interval } // NewTicker initializes and returns a ticker set at watchdogChecks (which is set at 1/3 of watchdogInterval). // It can be used by clients to trigger checks before using SendHeartbeat(). func (c *WatchDog) NewTicker() *time.Ticker { return time.NewTicker(c.checks) } go-systemd-5.1.0/resolved/000077500000000000000000000000001405322336300154345ustar00rootroot00000000000000go-systemd-5.1.0/resolved/dbus.go000066400000000000000000000355211405322336300167260ustar00rootroot00000000000000package resolved import ( "context" "errors" "fmt" "net" "os" "strconv" "github.com/godbus/dbus/v5" "github.com/miekg/dns" ) const ( dbusDest = "org.freedesktop.resolve1" dbusInterface = "org.freedesktop.resolve1.Manager" dbusPath = "/org/freedesktop/resolve1" ) // Conn represents a systemd-resolved dbus connection. type Conn struct { conn *dbus.Conn obj dbus.BusObject } // NewConn returns a new and ready to use dbus connection. // You must close that connection when you have been done with it. func NewConn() (*Conn, error) { conn, err := dbus.SystemBusPrivate() if err != nil { return nil, fmt.Errorf("failed to init private conn to system bus: %v", err) } methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(os.Getuid()))} err = conn.Auth(methods) if err != nil { conn.Close() return nil, fmt.Errorf("failed to auth with external method: %v", err) } err = conn.Hello() if err != nil { conn.Close() return nil, fmt.Errorf("failed to make hello call: %v", err) } return &Conn{ conn: conn, obj: conn.Object(dbusDest, dbus.ObjectPath(dbusPath)), }, nil } // Call wraps obj.CallWithContext by using 0 as flags and format the method with the dbus manager interface. func (c *Conn) Call(ctx context.Context, method string, args ...interface{}) *dbus.Call { return c.obj.CallWithContext(ctx, fmt.Sprintf("%s.%s", dbusInterface, method), 0, args...) } // Close closes the current dbus connection. func (c *Conn) Close() error { return c.conn.Close() } // ResolveHostname, ResolveAddress, ResolveRecord, ResolveService // The four methods above accept and return a 64-bit flags value. // In most cases passing 0 is sufficient and recommended. // However, the following flags are defined to alter the look-up const ( SD_RESOLVED_DNS = uint64(1) << 0 SD_RESOLVED_LLMNR_IPV4 = uint64(1) << 1 SD_RESOLVED_LLMNR_IPV6 = uint64(1) << 2 SD_RESOLVED_MDNS_IPV4 = uint64(1) << 3 SD_RESOLVED_MDNS_IPV6 = uint64(1) << 4 SD_RESOLVED_NO_CNAME = uint64(1) << 5 SD_RESOLVED_NO_TXT = uint64(1) << 6 SD_RESOLVED_NO_ADDRESS = uint64(1) << 7 SD_RESOLVED_NO_SEARCH = uint64(1) << 8 SD_RESOLVED_AUTHENTICATED = uint64(1) << 9 ) // Address represents an address returned by ResolveHostname. type Address struct { IfIndex int // network interface index Family int // can be either syscall.AF_INET or syscall.AF_INET6 Address net.IP // binary address } func (a Address) String() string { return fmt.Sprintf(`{ IfIndex: %d, Family: %d, IP: %s, }`, a.IfIndex, a.Family, a.Address.String()) } // ResolveHostname takes a hostname and resolves it to one or more IP addresses. // ctx: Context to use // ifindex: Network interface index where to look (0 means any) // name: Hostname // family: Which address family to look for (syscall.AF_UNSPEC, syscall.AF_INET, syscall.AF_INET6) // flags: Input flags parameter func (c *Conn) ResolveHostname(ctx context.Context, ifindex int, name string, family int, flags uint64) (addresses []Address, canonical string, outflags uint64, err error) { err = c.Call(ctx, "ResolveHostname", ifindex, name, family, flags).Store(&addresses, &canonical, &outflags) return } // Name represents a hostname returned by ResolveAddress. type Name struct { IfIndex int // network interface index Hostname string // hostname } func (n Name) String() string { return fmt.Sprintf(`{ IfIndex: %d, Name: %s, }`, n.IfIndex, n.Hostname) } // ResolveAddress takes a DNS resource record (RR) type, class and name // and retrieves the full resource record set (RRset), including the RDATA, for it. // ctx: Context to use // ifindex: Network interface index where to look (0 means any) // family: Address family (syscall.AF_INET, syscall.AF_INET6) // address: the binary address (4 or 16 bytes) // flags: Input flags parameter func (c *Conn) ResolveAddress(ctx context.Context, ifindex int, family int, address net.IP, flags uint64) (names []Name, outflags uint64, err error) { err = c.Call(ctx, "ResolveAddress", ifindex, family, address, flags).Store(&names, &outflags) return } // ResourceRecord represents a DNS RR as it returned by // by ResolveRecord. type ResourceRecord struct { IfIndex int // network interface index Type dns.Type // dns type Class dns.Class // dns class // The raw RR data starts with the RR's domain name, in the original casing, followed by the RR type, class, // TTL and RDATA, in the binary format documented in RFC 1035. For RRs that support name compression in the payload // (such as MX or PTR), the compression is expanded in the returned data. Data []byte } func (r ResourceRecord) String() string { return fmt.Sprintf(`{ IfIndex: %d, Type: %s, Class: %s, Data: %v, }`, r.IfIndex, r.Type.String(), r.Class.String(), r.Data) } // Unpack unpacks a ResourceRecord to dns.RR interface. func (r ResourceRecord) Unpack() (dns.RR, error) { rr, _, err := dns.UnpackRR(r.Data, 0) if err != nil { return nil, err } return rr, nil } // CNAME unpacks a ResourceRecord to *dns.CNAME. func (r ResourceRecord) CNAME() (*dns.CNAME, error) { rr, err := r.Unpack() if err != nil { return nil, err } if rr.Header().Rrtype != dns.TypeCNAME { return nil, errors.New("not an CNAME record type") } cname, ok := rr.(*dns.CNAME) if !ok { return nil, errors.New("dns.RR is not a *dns.CNAME") } return cname, nil } // MX unpacks a ResourceRecord to *dns.MX. func (r ResourceRecord) MX() (*dns.MX, error) { rr, err := r.Unpack() if err != nil { return nil, err } if rr.Header().Rrtype != dns.TypeMX { return nil, errors.New("not an MX record type") } mx, ok := rr.(*dns.MX) if !ok { return nil, errors.New("dns.RR is not a *dns.MX") } return mx, nil } // NS unpacks a ResourceRecord to *dns.NS. func (r ResourceRecord) NS() (*dns.NS, error) { rr, err := r.Unpack() if err != nil { return nil, err } if rr.Header().Rrtype != dns.TypeNS { return nil, errors.New("not an NS record type") } ns, ok := rr.(*dns.NS) if !ok { return nil, errors.New("dns.RR is not a *dns.NS") } return ns, nil } // SRV unpacks a ResourceRecord to *dns.SRV. func (r ResourceRecord) SRV() (*dns.SRV, error) { rr, err := r.Unpack() if err != nil { return nil, err } if rr.Header().Rrtype != dns.TypeSRV { return nil, errors.New("not an SRV record type") } srv, ok := rr.(*dns.SRV) if !ok { return nil, errors.New("dns.RR is not a *dns.SRV") } return srv, nil } // TXT unpacks a ResourceRecord to *dns.TXT. func (r ResourceRecord) TXT() (*dns.TXT, error) { rr, err := r.Unpack() if err != nil { return nil, err } if rr.Header().Rrtype != dns.TypeTXT { return nil, errors.New("not an TXT record type") } txt, ok := rr.(*dns.TXT) if !ok { return nil, errors.New("dns.RR is not a *dns.TXT") } return txt, nil } // ResolveRecord takes a DNS resource record (RR) type, class and name, and retrieves the full resource record set (RRset), including the RDATA, for it. // ctx: Context to use // ifindex: Network interface index where to look (0 means any) // name: Specifies the RR domain name to look up // class: 16-bit dns class // rtype: 16-bit dns type // flags: Input flags parameter func (c *Conn) ResolveRecord(ctx context.Context, ifindex int, name string, class dns.Class, rtype dns.Type, flags uint64) (records []ResourceRecord, outflags uint64, err error) { err = c.Call(ctx, "ResolveRecord", ifindex, name, class, rtype, flags).Store(&records, &outflags) return } // SRVRecord represents an service record as it returned // by ResolveService. type SRVRecord struct { Priority uint16 Weight uint16 Port uint16 Hostname string Addresses []Address CNAME string } func (r SRVRecord) String() string { return fmt.Sprintf(`{ Priority: %d, Weight: %d, Port: %d, Hostname: %s, Addresses: %v, }`, r.Priority, r.Weight, r.Port, r.Hostname, r.Addresses) } // TXTRecord represents a raw TXT RR string type TXTRecord []byte func (r TXTRecord) String() string { return string(r) } // ResolveService resolves a DNS SRV service record, as well as the hostnames referenced in it // and possibly an accompanying DNS-SD TXT record containing additional service metadata. // ctx: Context to use // ifindex: Network interface index where to look (0 means any) // name: the service name // stype: the service type (eg: _webdav._tcp) // domain: the service domain // family: Address family (syscall.AF_UNSPEC, syscall.AF_INET, syscall.AF_INET6) // flags: Input flags parameter func (c *Conn) ResolveService(ctx context.Context, ifindex int, name string, stype string, domain string, family int, flags uint64) (srvData []SRVRecord, txtData []TXTRecord, canonicalName string, canonicalType string, canonicalDomain string, outflags uint64, err error) { err = c.Call(ctx, "ResolveService", ifindex, name, stype, domain, family, flags).Store(&srvData, &txtData, &canonicalName, &canonicalType, &canonicalDomain, &outflags) return } // GetLink takes a network interface index and returns the object path // to the org.freedesktop.resolve1.Link object corresponding to it. // ctx: Context to use // ifindex: The network interface index to get link for func (c *Conn) GetLink(ctx context.Context, ifindex int) (path string, err error) { err = c.Call(ctx, "GetLink", ifindex).Store(&path) return } // LinkDNS represents a DNS server address to use in SetLinkDNS method. type LinkDNS struct { Family int // can be either syscall.AF_INET or syscall.AF_INET6 Address net.IP // binary address } // SetLinkDNS sets the DNS servers to use on a specific interface. // ctx: Context to use // ifindex: The network interface index to set // addrs: array of DNS server IP address records. func (c *Conn) SetLinkDNS(ctx context.Context, ifindex int, addrs []LinkDNS) (err error) { err = c.Call(ctx, "SetLinkDNS", ifindex, addrs).Store() return } type LinkDNSEx struct { Family int // can be either syscall.AF_INET or syscall.AF_INET6 Address net.IP // binary address Port uint16 // the port number Name string // the DNS Name } // SetLinkDNSEx is similar to SetLinkDNS(), but allows an IP port // (instead of the default 53) and DNS name to be specified for each DNS server. // The server name is used for Server Name Indication (SNI), which is useful when DNS-over-TLS is used. // ctx: Context to use // ifindex: The network interface index // addrs: array of DNS server IP address records. func (c *Conn) SetLinkDNSEx(ctx context.Context, ifindex int, addrs []LinkDNSEx) error { return c.Call(ctx, "SetLinkDNSEx", ifindex, addrs).Store() } type LinkDomain struct { Domain string // the domain name RoutingDomain bool // whether the specified domain shall be used as a search domain (false), or just as a routing domain (true). } // SetLinkDomains sets the search and routing domains to use on a specific network interface for DNS look-ups. // ctx: Context to use // ifindex: The network interface index // domains: array of domains func (c *Conn) SetLinkDomains(ctx context.Context, ifindex int, domains []LinkDomain) error { return c.Call(ctx, "SetLinkDomains", ifindex, domains).Store() } // SetLinkDefaultRoute specifies whether the link shall be used as the default route for name queries // ctx: Context to use // ifindex: The network interface index // enable: enable/disable link as default route. func (c *Conn) SetLinkDefaultRoute(ctx context.Context, ifindex int, enable bool) error { return c.Call(ctx, "SetLinkDefaultRoute", ifindex, enable).Store() } // SetLinkLLMNR enables or disables LLMNR support on a specific network interface. // ctx: Context to use // ifindex: The network interface index // mode: either empty or one of "yes", "no" or "resolve". func (c *Conn) SetLinkLLMNR(ctx context.Context, ifindex int, mode string) error { return c.Call(ctx, "SetLinkLLMNR", ifindex, mode).Store() } // SetLinkMulticastDNS enables or disables MulticastDNS support on a specific interface. // ctx: Context to use // ifindex: The network interface index // mode: either empty or one of "yes", "no" or "resolve". func (c *Conn) SetLinkMulticastDNS(ctx context.Context, ifindex int, mode string) error { return c.Call(ctx, "SetLinkMulticastDNS", ifindex, mode).Store() } // SetLinkDNSOverTLS enables or disables enables or disables DNS-over-TLS on a specific network interface. // ctx: Context to use // ifindex: The network interface index // mode: either empty or one of "yes", "no", or "opportunistic" func (c *Conn) SetLinkDNSOverTLS(ctx context.Context, ifindex int, mode string) error { return c.Call(ctx, "SetLinkDNSOverTLS", ifindex, mode).Store() } // SetLinkDNSSEC enables or disables DNSSEC validation on a specific network interface. // ctx: Context to use // ifindex: The network interface index // mode: either empty or one of "yes", "no", or "allow-downgrade" func (c *Conn) SetLinkDNSSEC(ctx context.Context, ifindex int, mode string) error { return c.Call(ctx, "SetLinkDNSSEC", ifindex, mode).Store() } // SetLinkDNSSECNegativeTrustAnchors configures DNSSEC Negative Trust Anchors (NTAs) for a specific network interface. // ctx: Context to use // ifindex: The network interface index // names: array of domains func (c *Conn) SetLinkDNSSECNegativeTrustAnchors(ctx context.Context, ifindex int, names []string) error { return c.Call(ctx, "SetLinkDNSSECNegativeTrustAnchors", ifindex, names).Store() } // RevertLink reverts all per-link settings to the defaults on a specific network interface. // ctx: Context to use // ifindex: The network interface index. func (c *Conn) RevertLink(ctx context.Context, ifindex int) error { return c.Call(ctx, "RevertLink", ifindex).Store() } // RegisterService func (c *Conn) RegisterService(ctx context.Context, name string, nameTemplate string, stype string, svcPort uint16, svcPriority uint16, svcWeight uint16, txtData []TXTRecord) (svcPath string, err error) { err = c.Call(ctx, "RegisterService", name, nameTemplate, stype, svcPort, svcPriority, svcWeight, txtData).Store(&svcPath) return } // UnregisterService func (c *Conn) UnregisterService(ctx context.Context, svcPath string) error { return c.Call(ctx, "UnregisterService", svcPath).Store() } // ResetStatistics resets the various statistics counters that systemd-resolved maintains to zero. func (c *Conn) ResetStatistics(ctx context.Context) error { return c.Call(ctx, "ResetStatistics").Store() } // FlushCaches func (c *Conn) FlushCaches(ctx context.Context) error { return c.Call(ctx, "FlushCaches").Store() } // ResetServerFeatures func (c *Conn) ResetServerFeatures(ctx context.Context) error { return c.Call(ctx, "ResetServerFeatures").Store() } type Link struct { obj dbus.BusObject } func NewLink(c *Conn, path string) Link { return Link{ obj: c.conn.Object(dbusDest, dbus.ObjectPath(path)), } } // TODO // SetDNS(in a(iay) addresses); // SetDNSEx(in a(iayqs) addresses); // SetDomains(in a(sb) domains); // SetDefaultRoute(in b enable); // SetLLMNR(in s mode); // SetMulticastDNS(in s mode); // SetDNSOverTLS(in s mode); // SetDNSSEC(in s mode); // SetDNSSECNegativeTrustAnchors(in as names); // Revert(); go-systemd-5.1.0/resolved/resolver.go000066400000000000000000000320271405322336300176300ustar00rootroot00000000000000package resolved import ( "context" "errors" "net" "net/http" "runtime" "sort" "syscall" "time" "github.com/miekg/dns" "golang.org/x/net/idna" ) // Note: This is still under development and very experimental, do not use it in production. // resolver is the interface to implements the same methods as the net.Resolver type resolver interface { LookupAddr(ctx context.Context, addr string) (names []string, err error) LookupCNAME(ctx context.Context, host string) (cname string, err error) LookupHost(ctx context.Context, host string) (addrs []string, err error) LookupIP(ctx context.Context, network, host string) ([]net.IP, error) LookupIPAddr(ctx context.Context, host string) ([]net.IPAddr, error) LookupMX(ctx context.Context, name string) ([]*net.MX, error) LookupNS(ctx context.Context, name string) ([]*net.NS, error) LookupPort(ctx context.Context, network, service string) (port int, err error) LookupSRV(ctx context.Context, service, proto, name string) (cname string, addrs []*net.SRV, err error) LookupTXT(ctx context.Context, name string) ([]string, error) } var ( // ensure that types implement resolver interface _ resolver = &Resolver{} _ resolver = &net.Resolver{} ) // Resolver represents the systemd-resolved resolver // throught dbus connection. type Resolver struct { conn *Conn dialer *net.Dialer profile *idna.Profile } type resolverOption func(r *Resolver) error // WithConn allow you to use a custom systemd-resolved dbus connection. func WithConn(c *Conn) resolverOption { return func(r *Resolver) error { if c == nil { return errors.New("conn is nil") } r.conn = c return nil } } // WithDialer allow you to use a custom net.Dialer. func WithDialer(d *net.Dialer) resolverOption { return func(r *Resolver) error { if d == nil { return errors.New("dialer is nil") } r.dialer = d return nil } } // WithProfile allow you to use custom idna.Profile. func WithProfile(p *idna.Profile) resolverOption { return func(r *Resolver) error { if p == nil { return errors.New("profile is nil") } r.profile = p return nil } } // NewResolver returns a new systemd Resolver with an initialized dbus connection. // it's up to you to close that connection when you have been done with the Resolver. func NewResolver(opts ...resolverOption) (*Resolver, error) { r := &Resolver{} for _, opt := range opts { opt(r) } if r.conn == nil { var err error r.conn, err = NewConn() if err != nil { return nil, err } } if r.dialer == nil { r.dialer = &net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, } } if r.profile == nil { r.profile = idna.New() } return r, nil } // Close closes the current dbus connection. // You need to close the connection when you've done with it. func (r *Resolver) Close() error { return r.conn.Close() } // DialContext resolves address using systemd-network and use internal dialer with the resolved ip address. // It is useful when it comes to integration with go standard library. func (r *Resolver) DialContext(ctx context.Context, network string, address string) (net.Conn, error) { host, port, err := net.SplitHostPort(address) if err != nil { return nil, err } addrs, _, _, err := r.conn.ResolveHostname(ctx, 0, host, syscall.AF_UNSPEC, 0) if err != nil { return nil, err } for _, addr := range addrs { if addr.Address.To4() == nil { // prefer ipv6 address = addr.Address.String() break } address = addr.Address.String() } return r.dialer.DialContext(ctx, network, net.JoinHostPort(address, port)) } // HTTPClient returns a new http.Client with systemd-resolved as resolver // and idle connections + keepalives disabled. func (r *Resolver) HTTPClient() *http.Client { transport := r.pooledTransport() transport.DisableKeepAlives = true transport.MaxIdleConnsPerHost = -1 return &http.Client{ Transport: transport, } } // HTTPPooledClient returns a new http.Client with systemd-resolved as resolver // and similar default values to http.DefaultTransport. // Do not use this for transient transports as // it can leak file descriptors over time. Only use this for transports that // will be re-used for the same host(s). func (r *Resolver) HTTPPooledClient() *http.Client { return &http.Client{ Transport: r.pooledTransport(), } } func (r *Resolver) pooledTransport() *http.Transport { transport := &http.Transport{ Proxy: http.ProxyFromEnvironment, DialContext: r.DialContext, MaxIdleConns: 100, IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, ForceAttemptHTTP2: true, MaxIdleConnsPerHost: runtime.GOMAXPROCS(0) + 1, } return transport } // LookupHost looks up the given host using the systemd-resolved resolver. // It returns a slice of that host's addresses. func (r *Resolver) LookupHost(ctx context.Context, host string) (addrs []string, err error) { if host == "" { return nil, &net.DNSError{Err: "no such host", Name: host, IsNotFound: true} } addresses, _, _, err := r.conn.ResolveHostname(ctx, 0, host, syscall.AF_UNSPEC, 0) if err != nil { return nil, err } addrs = make([]string, len(addresses)) for i, addr := range addresses { addrs[i] = addr.Address.String() } return } // LookupAddr performs a reverse lookup for the given address, returning a list // of names mapping to that address. func (r *Resolver) LookupAddr(ctx context.Context, addr string) (names []string, err error) { ip := net.ParseIP(addr) if ip == nil { return nil, &net.DNSError{Err: "unrecognized address", Name: addr} } var family int if ipv4 := ip.To4(); ipv4 != nil { // use 4-byte representation ip = ipv4 family = syscall.AF_INET } else { family = syscall.AF_INET6 } hostnames, _, err := r.conn.ResolveAddress(ctx, 0, family, ip, 0) if err != nil { return nil, err } names = make([]string, len(hostnames)) for i, name := range hostnames { names[i] = fullyQualified(name.Hostname) } return } // LookupIP looks up host for the given network using the systemd-resolved resolver. // It returns a slice of that host's IP addresses of the type specified by network. // network must be one of "ip", "ip4" or "ip6". func (r *Resolver) LookupIP(ctx context.Context, network, host string) ([]net.IP, error) { if host == "" { return nil, &net.DNSError{Err: "no such host", Name: host, IsNotFound: true} } var family int switch network { case "ip": family = syscall.AF_UNSPEC case "ip4": family = syscall.AF_INET case "ip6": family = syscall.AF_INET6 default: return nil, errors.New("bad network") } addresses, _, _, err := r.conn.ResolveHostname(ctx, 0, host, family, 0) if err != nil { return nil, err } addrs := make([]net.IP, len(addresses)) for i, addr := range addresses { addrs[i] = addr.Address } return addrs, nil } // LookupIPAddr looks up host using the systemd-resolved resolver. // It returns a slice of that host's IPv4 and IPv6 addresses. func (r *Resolver) LookupIPAddr(ctx context.Context, host string) ([]net.IPAddr, error) { if host == "" { return nil, &net.DNSError{Err: "no such host", Name: host, IsNotFound: true} } addresses, _, _, err := r.conn.ResolveHostname(ctx, 0, host, syscall.AF_UNSPEC, 0) if err != nil { return nil, err } addrs := make([]net.IPAddr, len(addresses)) for i, addr := range addresses { addrs[i] = net.IPAddr{ IP: addr.Address, } } return addrs, nil } // LookupCNAME returns the canonical name for the given host. func (r *Resolver) LookupCNAME(ctx context.Context, host string) (string, error) { var ok bool if host, ok = r.IsDomainName(host); !ok { return "", &net.DNSError{Err: "no such host", Name: host, IsNotFound: true} } records, _, err := r.conn.ResolveRecord(ctx, 0, host, dns.ClassINET, dns.Type(dns.TypeCNAME), 0) if err != nil { return "", err } for _, record := range records { recordCNAME, err := record.CNAME() if err != nil { return "", err } return recordCNAME.Target, nil } return "", &net.DNSError{Err: "no such host", Name: host, IsNotFound: true} } // LookupMX returns the DNS MX records for the given domain name sorted by preference. func (r *Resolver) LookupMX(ctx context.Context, name string) ([]*net.MX, error) { var ok bool if name, ok = r.IsDomainName(name); !ok { return nil, &net.DNSError{Err: "no such host", Name: name, IsNotFound: true} } records, _, err := r.conn.ResolveRecord(ctx, 0, name, dns.ClassINET, dns.Type(dns.TypeMX), 0) if err != nil { return nil, err } mxs := make([]*net.MX, len(records)) for i, record := range records { mx, err := record.MX() if err != nil { return nil, err } mxs[i] = &net.MX{ Host: mx.Mx, Pref: mx.Preference, } } sort.Slice(mxs, func(i, j int) bool { return mxs[i].Pref < mxs[j].Pref }) return mxs, nil } // LookupNS returns the DNS NS records for the given domain name. func (r *Resolver) LookupNS(ctx context.Context, name string) ([]*net.NS, error) { var ok bool if name, ok = r.IsDomainName(name); !ok { return nil, &net.DNSError{Err: "no such host", Name: name, IsNotFound: true} } records, _, err := r.conn.ResolveRecord(ctx, 0, name, dns.ClassINET, dns.Type(dns.TypeNS), 0) if err != nil { return nil, err } nss := make([]*net.NS, len(records)) for i, record := range records { ns, err := record.NS() if err != nil { return nil, err } nss[i] = &net.NS{ Host: ns.Ns, } } return nss, nil } // LookupPort looks up the port for the given network and service. func (r *Resolver) LookupPort(ctx context.Context, network, service string) (port int, err error) { // this is not supported because i don't want to implement again what's inside the go standard library // like the port map filled with /etc/service etc... err = errors.New("not supported yet") return } // LookupSRV tries to resolve an SRV query of the given service, protocol, and domain name. // The proto is "tcp" or "udp". The returned records are sorted by priority. func (r *Resolver) LookupSRV(ctx context.Context, service, proto, name string) (cname string, addrs []*net.SRV, err error) { var target string if service == "" && proto == "" { target = name } else { target = "_" + service + "._" + proto + "." + name } srvData, _, _, canonicalType, canonicalDomain, _, err := r.conn.ResolveService(ctx, 0, "", "", target, syscall.AF_UNSPEC, 0) if err != nil { return } addrs = make([]*net.SRV, len(srvData)) for i, srv := range srvData { addrs[i] = &net.SRV{ Target: fullyQualified(srv.Hostname), Port: srv.Port, Priority: srv.Priority, Weight: srv.Weight, } } sort.Slice(addrs, func(i, j int) bool { return addrs[i].Priority < addrs[j].Priority }) if canonicalType != "" { cname = fullyQualified(canonicalType + "." + canonicalDomain) } else { cname = fullyQualified(canonicalDomain) } return } // LookupTXT returns the DNS TXT records for the given domain name. func (r *Resolver) LookupTXT(ctx context.Context, name string) ([]string, error) { var ok bool if name, ok = r.IsDomainName(name); !ok { return nil, &net.DNSError{Err: "no such host", Name: name, IsNotFound: true} } records, _, err := r.conn.ResolveRecord(ctx, 0, name, dns.ClassINET, dns.Type(dns.TypeTXT), 0) if err != nil { return nil, err } txts := make([]string, 0, len(records)) for _, record := range records { txt, err := record.TXT() if err != nil { return nil, err } txts = append(txts, txt.Txt...) } return txts, nil } // IsDomainName tries to convert name to ASCII (IANA conversion) if name is not a strict domain name (see RFC 1035) // It returns false if name is not a domain before and after ASCII conversion. // It uses isDomainName from go standard library. func (r *Resolver) IsDomainName(name string) (string, bool) { if !isDomainName(name) { var err error name, err = r.profile.ToASCII(name) if err != nil { return name, false } if !isDomainName(name) { return name, false } } return name, true } func fullyQualified(s string) string { b := []byte(s) hasDots := false for _, x := range b { if x == '.' { hasDots = true break } } if hasDots && b[len(b)-1] != '.' { b = append(b, '.') } return string(b) } // this function comes from go standard library // there is issues about it since it denied some valid domains. // see: https://github.com/golang/go/issues/17659 func isDomainName(s string) bool { l := len(s) if l == 0 || l > 254 || l == 254 && s[l-1] != '.' { return false } last := byte('.') nonNumeric := false // true once we've seen a letter or hyphen partlen := 0 for i := 0; i < len(s); i++ { c := s[i] switch { default: return false case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '_': nonNumeric = true partlen++ case '0' <= c && c <= '9': // fine partlen++ case c == '-': // Byte before dash cannot be dot. if last == '.' { return false } partlen++ nonNumeric = true case c == '.': // Byte before dot cannot be dot, dash. if last == '.' || last == '-' { return false } if partlen > 63 || partlen == 0 { return false } partlen = 0 } last = c } if last == '-' || partlen > 63 { return false } return nonNumeric } go-systemd-5.1.0/resolved/resolver_test.go000066400000000000000000000266761405322336300207040ustar00rootroot00000000000000package resolved import ( "context" "net" "net/http" "sort" "testing" ) // In order to run the test make sure that systemd-resolved resolver query the same dns server as the go one. const ( lookupHost = "google.com" lookupAddr4 = "142.250.178.142" lookupAddr6 = "2a00:1450:4007:81a::200e" lookupCNAMEHost = "en.wikipedia.org" lookupSRVDomain = "google.com" lookupSRVService = "xmpp-server" lookupSRVProto = "tcp" lookupSRVServiceDomain = "_xmpp-server._tcp.google.com" getUrl = "https://google.com" ) func TestLookupHost(t *testing.T) { sysdResolver, err := NewResolver() if err != nil { t.Fatal(err) } defer sysdResolver.Close() ctx := context.Background() sysdAddrs, err := sysdResolver.LookupHost(ctx, lookupHost) if err != nil { t.Fatal(err) } goResolver := &net.Resolver{} goAddrs, err := goResolver.LookupHost(ctx, lookupHost) if err != nil { t.Fatal(err) } if len(goAddrs) != len(sysdAddrs) { t.Fatal("len(goAddrs) != len(sysdAddrs)", len(goAddrs), len(sysdAddrs)) } sort.Strings(sysdAddrs) sort.Strings(goAddrs) for i, sAddr := range sysdAddrs { goAddr := goAddrs[i] if goAddr != sAddr { t.Error("goAddr != sAddr", goAddr, sAddr) } } } func TestLookupAddr4(t *testing.T) { sysdResolver, err := NewResolver() if err != nil { t.Fatal(err) } defer sysdResolver.Close() ctx := context.Background() sysdNames, err := sysdResolver.LookupAddr(ctx, lookupAddr4) if err != nil { t.Fatal(err) } goResolver := &net.Resolver{} goNames, err := goResolver.LookupAddr(ctx, lookupAddr4) if err != nil { t.Fatal(err) } if len(goNames) != len(sysdNames) { t.Fatal("len(goNames) != len(sysdNames)", len(goNames), len(sysdNames)) } sort.Strings(goNames) sort.Strings(sysdNames) for i, sName := range sysdNames { goName := goNames[i] if goName != sName { t.Error("goName != sName", goName, sName) } } } func TestLookupAddr6(t *testing.T) { sysdResolver, err := NewResolver() if err != nil { t.Fatal(err) } defer sysdResolver.Close() ctx := context.Background() sysdNames, err := sysdResolver.LookupAddr(ctx, lookupAddr6) if err != nil { t.Fatal(err) } goResolver := &net.Resolver{} goNames, err := goResolver.LookupAddr(ctx, lookupAddr6) if err != nil { t.Fatal(err) } if len(goNames) != len(sysdNames) { t.Fatal("len(goNames) != len(sysdNames)", len(goNames), len(sysdNames)) } sort.Strings(goNames) sort.Strings(sysdNames) for i, sName := range sysdNames { goName := goNames[i] if goName != sName { t.Error("goName != sName", goName, sName) } } } func TestLookupIP(t *testing.T) { sysdResolver, err := NewResolver() if err != nil { t.Fatal(err) } defer sysdResolver.Close() ctx := context.Background() sysdAddrs, err := sysdResolver.LookupIP(ctx, "ip", lookupHost) if err != nil { t.Fatal(err) } goResolver := &net.Resolver{} goAddrs, err := goResolver.LookupIP(ctx, "ip", lookupHost) if err != nil { t.Fatal(err) } if len(goAddrs) != len(sysdAddrs) { t.Fatal("len(goAddrs) != len(sysdAddrs)", len(goAddrs), len(sysdAddrs)) } sort.Slice(sysdAddrs, func(i, j int) bool { return sysdAddrs[i].String() < sysdAddrs[j].String() }) sort.Slice(goAddrs, func(i, j int) bool { return goAddrs[i].String() < goAddrs[j].String() }) for i, sAddr := range sysdAddrs { goAddr := goAddrs[i] if goAddr.String() != sAddr.String() { t.Error("goAddr != sAddr", goAddr, sAddr) } } } func TestLookupIP4(t *testing.T) { sysdResolver, err := NewResolver() if err != nil { t.Fatal(err) } defer sysdResolver.Close() ctx := context.Background() sysdAddrs, err := sysdResolver.LookupIP(ctx, "ip4", lookupHost) if err != nil { t.Fatal(err) } goResolver := &net.Resolver{} goAddrs, err := goResolver.LookupIP(ctx, "ip4", lookupHost) if err != nil { t.Fatal(err) } if len(goAddrs) != len(sysdAddrs) { t.Fatal("len(goAddrs) != len(sysdAddrs)", len(goAddrs), len(sysdAddrs)) } sort.Slice(sysdAddrs, func(i, j int) bool { return sysdAddrs[i].String() < sysdAddrs[j].String() }) sort.Slice(goAddrs, func(i, j int) bool { return goAddrs[i].String() < goAddrs[j].String() }) for i, sAddr := range sysdAddrs { goAddr := goAddrs[i] if goAddr.String() != sAddr.String() { t.Error("goAddr != sAddr", goAddr, sAddr) } } } func TestLookupIP6(t *testing.T) { sysdResolver, err := NewResolver() if err != nil { t.Fatal(err) } defer sysdResolver.Close() ctx := context.Background() sysdAddrs, err := sysdResolver.LookupIP(ctx, "ip6", lookupHost) if err != nil { t.Fatal(err) } goResolver := &net.Resolver{} goAddrs, err := goResolver.LookupIP(ctx, "ip6", lookupHost) if err != nil { t.Fatal(err) } if len(goAddrs) != len(sysdAddrs) { t.Fatal("len(goAddrs) != len(sysdAddrs)", len(goAddrs), len(sysdAddrs)) } sort.Slice(sysdAddrs, func(i, j int) bool { return sysdAddrs[i].String() < sysdAddrs[j].String() }) sort.Slice(goAddrs, func(i, j int) bool { return goAddrs[i].String() < goAddrs[j].String() }) for i, sAddr := range sysdAddrs { goAddr := goAddrs[i] if goAddr.String() != sAddr.String() { t.Error("goAddr != sAddr", goAddr, sAddr) } } } func TestLookupIPAddr(t *testing.T) { sysdResolver, err := NewResolver() if err != nil { t.Fatal(err) } defer sysdResolver.Close() ctx := context.Background() sysdAddrs, err := sysdResolver.LookupIPAddr(ctx, lookupHost) if err != nil { t.Fatal(err) } goResolver := &net.Resolver{} goAddrs, err := goResolver.LookupIPAddr(ctx, lookupHost) if err != nil { t.Fatal(err) } if len(goAddrs) != len(sysdAddrs) { t.Fatal("len(goAddrs) != len(sysdAddrs)", len(goAddrs), len(sysdAddrs)) } sort.Slice(sysdAddrs, func(i, j int) bool { return sysdAddrs[i].String() < sysdAddrs[j].String() }) sort.Slice(goAddrs, func(i, j int) bool { return goAddrs[i].String() < goAddrs[j].String() }) for i, sAddr := range sysdAddrs { goAddr := goAddrs[i] if goAddr.String() != sAddr.String() { t.Error("goAddr != sAddr", goAddr, sAddr) } if goAddr.Zone != sAddr.Zone { t.Error("goAddr .Zone!= sAddr.Zone", goAddr.Zone, sAddr.Zone) } } } func TestLookupCNAME(t *testing.T) { sysdResolver, err := NewResolver() if err != nil { t.Fatal(err) } defer sysdResolver.Close() ctx := context.Background() sysdCNAME, err := sysdResolver.LookupCNAME(ctx, lookupCNAMEHost) if err != nil { t.Fatal(err) } goResolver := &net.Resolver{} goCNAME, err := goResolver.LookupCNAME(ctx, lookupCNAMEHost) if err != nil { t.Fatal(err) } if goCNAME != sysdCNAME { t.Error("goCNAME != sysdCNAME", goCNAME, sysdCNAME) } } func TestLookupMX(t *testing.T) { sysdResolver, err := NewResolver() if err != nil { t.Fatal(err) } defer sysdResolver.Close() ctx := context.Background() sysdMxs, err := sysdResolver.LookupMX(ctx, lookupHost) if err != nil { t.Fatal(err) } goResolver := &net.Resolver{} goMxs, err := goResolver.LookupMX(ctx, lookupHost) if err != nil { t.Fatal(err) } if len(goMxs) != len(sysdMxs) { t.Fatal("len(goMxs) != len(sysdMxs)", len(goMxs), len(sysdMxs)) } for i, sMx := range sysdMxs { goMx := goMxs[i] if goMx.Host != sMx.Host { t.Error("goMx.Host != sMx.Host", goMx.Host, sMx.Host) } if goMx.Pref != sMx.Pref { t.Error("goMx.Pref != sMx.Pref", goMx.Pref, sMx.Pref) } } } func TestLookupNS(t *testing.T) { sysdResolver, err := NewResolver() if err != nil { t.Fatal(err) } defer sysdResolver.Close() ctx := context.Background() sysdNss, err := sysdResolver.LookupNS(ctx, lookupHost) if err != nil { t.Fatal(err) } goResolver := &net.Resolver{} goNss, err := goResolver.LookupNS(ctx, lookupHost) if err != nil { t.Fatal(err) } if len(goNss) != len(sysdNss) { t.Fatal("len(goNss) != len(sysdNss)", len(goNss), len(sysdNss)) } sort.Slice(sysdNss, func(i, j int) bool { return sysdNss[i].Host < sysdNss[j].Host }) sort.Slice(goNss, func(i, j int) bool { return goNss[i].Host < goNss[j].Host }) for i, sNs := range sysdNss { goNs := goNss[i] if goNs.Host != sNs.Host { t.Error("goNs.Host != sNs.Host", goNs.Host, sNs.Host) } } } func TestLookupSRV(t *testing.T) { sysdResolver, err := NewResolver() if err != nil { t.Fatal(err) } defer sysdResolver.Close() ctx := context.Background() sysdCNAME, sysdSrvs, err := sysdResolver.LookupSRV(ctx, lookupSRVService, lookupSRVProto, lookupSRVDomain) if err != nil { t.Fatal(err) } goResolver := &net.Resolver{} goCNAME, goSrvs, err := goResolver.LookupSRV(ctx, lookupSRVService, lookupSRVProto, lookupSRVDomain) if err != nil { t.Fatal(err) } if sysdCNAME != goCNAME { t.Fatal("sysdCNAME != goCNAME", sysdCNAME, goCNAME) } if len(goSrvs) != len(sysdSrvs) { t.Fatal("len(goSrvs) != len(sysdSrvs)", len(goSrvs), len(sysdSrvs)) } sort.Slice(sysdSrvs, func(i, j int) bool { return sysdSrvs[i].Target < sysdSrvs[j].Target }) sort.Slice(goSrvs, func(i, j int) bool { return goSrvs[i].Target < goSrvs[j].Target }) for i, sSrv := range sysdSrvs { goSrv := goSrvs[i] if goSrv.Target != sSrv.Target { t.Error("goSrv.Target != sSrv.Target", goSrv.Target, sSrv.Target) } if goSrv.Port != sSrv.Port { t.Error("goSrv.Port != sSrv.Port", goSrv.Port, sSrv.Port) } if goSrv.Priority != sSrv.Priority { t.Error("goSrv.Priority != sSrv.Priority", goSrv.Priority, sSrv.Priority) } if goSrv.Weight != sSrv.Weight { t.Error("goSrv.Weight != sSrv.Weight", goSrv.Weight, sSrv.Weight) } } } func TestLookupTXT(t *testing.T) { sysdResolver, err := NewResolver() if err != nil { t.Fatal(err) } defer sysdResolver.Close() ctx := context.Background() sysdTxts, err := sysdResolver.LookupTXT(ctx, lookupHost) if err != nil { t.Fatal(err) } goResolver := &net.Resolver{} goTxts, err := goResolver.LookupTXT(ctx, lookupHost) if err != nil { t.Fatal(err) } if len(goTxts) != len(sysdTxts) { t.Fatal("len(goTxts) != len(sysdTxts)", len(goTxts), len(sysdTxts)) } sort.Strings(sysdTxts) sort.Strings(goTxts) for i, sTxt := range sysdTxts { goTxt := goTxts[i] if goTxt != sTxt { t.Error("goTxt != sTxt", goTxt, sTxt) } } } func BenchmarkLookupHostGoResolver(b *testing.B) { r := &net.Resolver{} ctx := context.Background() for n := 0; n < b.N; n++ { _, err := r.LookupHost(ctx, lookupHost) if err != nil { b.Error(err) continue } } } func BenchmarkLookupHostSystemdResolver(b *testing.B) { r, err := NewResolver() if err != nil { b.Fatal(err) } defer r.Close() ctx := context.Background() for n := 0; n < b.N; n++ { _, err := r.LookupHost(ctx, lookupHost) if err != nil { b.Error(err) continue } } } func BenchmarkLookupAddrGoResolver(b *testing.B) { r := &net.Resolver{} ctx := context.Background() for n := 0; n < b.N; n++ { _, err := r.LookupAddr(ctx, lookupAddr4) if err != nil { b.Error(err) continue } } } func BenchmarkLookupAddrSystemdResolver(b *testing.B) { r, err := NewResolver() if err != nil { b.Fatal(err) } defer r.Close() ctx := context.Background() for n := 0; n < b.N; n++ { _, err := r.LookupAddr(ctx, lookupAddr4) if err != nil { b.Error(err) continue } } } func BenchmarkHTTPClientGoResolver(b *testing.B) { httpCli := &http.Client{} for n := 0; n < b.N; n++ { resp, err := httpCli.Get(getUrl) if err != nil { b.Error(err) continue } resp.Body.Close() } } func BenchmarkHTTPClientSystemdResolver(b *testing.B) { r, err := NewResolver() if err != nil { b.Fatal(err) } defer r.Close() httpCli := r.HTTPClient() for n := 0; n < b.N; n++ { resp, err := httpCli.Get(getUrl) if err != nil { b.Error(err) continue } resp.Body.Close() } } go-systemd-5.1.0/sysd.go000066400000000000000000000004761405322336300151310ustar00rootroot00000000000000package sysd import "os" // GetInvocationID returns the systemd invocation ID. // If exists is false, we have not been launched by systemd. // Present since systemd v232: https://github.com/systemd/systemd/blob/v232/NEWS#L254 func GetInvocationID() (ID string, exists bool) { return os.LookupEnv("INVOCATION_ID") }