summaryrefslogtreecommitdiffhomepage
path: root/net/dns
AgeCommit message (Collapse)AuthorFilesLines
2026-01-26net/dns: add test for DoH upgrade of system DNSAndrew Dunham2-0/+242
Someone asked me if we use DNS-over-HTTPS if the system's resolver is an IP address that supports DoH and there's no global nameserver set (i.e. no "Override DNS servers" set). I didn't know the answer offhand, and it took a while for me to figure it out. The answer is yes, in cases where we take over the system's DNS configuration and read the base config, we do upgrade any DoH-capable resolver to use DoH. Here's a test that verifies this behaviour (and hopefully helps as documentation the next time someone has this question). Updates #cleanup Signed-off-by: Andrew Dunham <andrew@tailscale.com>
2026-01-26net/dns/publicdns: support CIRA Canadian ShieldAndrew Dunham1-0/+20
RELNOTE=Add DNS-over-HTTPS support for CIRA Canadian Shield Fixes #18524 Signed-off-by: Andrew Dunham <andrew@tailscale.com>
2026-01-23all: remove AUTHORS file and references to itWill Norris54-54/+54
This file was never truly necessary and has never actually been used in the history of Tailscale's open source releases. A Brief History of AUTHORS files --- The AUTHORS file was a pattern developed at Google, originally for Chromium, then adopted by Go and a bunch of other projects. The problem was that Chromium originally had a copyright line only recognizing Google as the copyright holder. Because Google (and most open source projects) do not require copyright assignemnt for contributions, each contributor maintains their copyright. Some large corporate contributors then tried to add their own name to the copyright line in the LICENSE file or in file headers. This quickly becomes unwieldy, and puts a tremendous burden on anyone building on top of Chromium, since the license requires that they keep all copyright lines intact. The compromise was to create an AUTHORS file that would list all of the copyright holders. The LICENSE file and source file headers would then include that list by reference, listing the copyright holder as "The Chromium Authors". This also become cumbersome to simply keep the file up to date with a high rate of new contributors. Plus it's not always obvious who the copyright holder is. Sometimes it is the individual making the contribution, but many times it may be their employer. There is no way for the proejct maintainer to know. Eventually, Google changed their policy to no longer recommend trying to keep the AUTHORS file up to date proactively, and instead to only add to it when requested: https://opensource.google/docs/releasing/authors. They are also clear that: > Adding contributors to the AUTHORS file is entirely within the > project's discretion and has no implications for copyright ownership. It was primarily added to appease a small number of large contributors that insisted that they be recognized as copyright holders (which was entirely their right to do). But it's not truly necessary, and not even the most accurate way of identifying contributors and/or copyright holders. In practice, we've never added anyone to our AUTHORS file. It only lists Tailscale, so it's not really serving any purpose. It also causes confusion because Tailscalars put the "Tailscale Inc & AUTHORS" header in other open source repos which don't actually have an AUTHORS file, so it's ambiguous what that means. Instead, we just acknowledge that the contributors to Tailscale (whoever they are) are copyright holders for their individual contributions. We also have the benefit of using the DCO (developercertificate.org) which provides some additional certification of their right to make the contribution. The source file changes were purely mechanical with: git ls-files | xargs sed -i -e 's/\(Tailscale Inc &\) AUTHORS/\1 contributors/g' Updates #cleanup Change-Id: Ia101a4a3005adb9118051b3416f5a64a4a45987d Signed-off-by: Will Norris <will@tailscale.com>
2025-12-17net/dns/resolver: log source IP of forwarded queriesAndrew Dunham2-2/+50
When the TS_DEBUG_DNS_FORWARD_SEND envknob is turned on, also log the source IP:port of the query that tailscaled is forwarding. Updates tailscale/corp#35374 Signed-off-by: Andrew Dunham <andrew@tailscale.com>
2025-12-09net/dns: retrample resolve.conf when another process has trampled it (#18069)Claus Lensbøl15-42/+258
When using the resolve.conf file for setting DNS, it is possible that some other services will trample the file and overwrite our set DNS server. Experiments has shown this to be a racy error depending on how quickly processes start. Make an attempt to trample back the file a limited number of times if the file is changed. Updates #16635 Signed-off-by: Claus Lensbøl <claus@tailscale.com>
2025-11-18all: rename variables with lowercase-l/uppercase-IAlex Chan1-2/+2
See http://go/no-ell Signed-off-by: Alex Chan <alexc@tailscale.com> Updates #cleanup Change-Id: I8c976b51ce7a60f06315048b1920516129cc1d5d
2025-11-16syncs: add Mutex/RWMutex alias/wrappers for future mutex debuggingBrad Fitzpatrick4-6/+7
Updates #17852 Change-Id: I477340fb8e40686870e981ade11cd61597c34a20 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2025-10-08net/dns, wgengine: use viewer/cloner for ConfigBrad Fitzpatrick4-86/+214
Per earlier TODO. Updates #17506 Change-Id: I21fe851c4bcced98fcee844cb428ca9c2f6b0588 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2025-10-08net/dns, ipn/ipnlocal: fix regressions from change moving away from deephashBrad Fitzpatrick2-2/+75
I got sidetracked apparently and never finished writing this Clone code in 316afe7d02babc (#17448). (It really should use views instead.) And then I missed one of the users of "routerChanged" that was broken up into "routerChanged" vs "dnsChanged". This broke integration tests elsewhere. Fixes #17506 Change-Id: I533bf0fcf3da9ac6eb4a6cdef03b8df2c1fb4c8e Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2025-10-06util/checkchange: stop using deephash everywhereBrad Fitzpatrick1-0/+21
Saves 45 KB from the min build, no longer pulling in deephash or util/hashx, both with unsafe code. It can actually be more efficient to not use deephash, as you don't have to walk all bytes of all fields recursively to answer that two things are not equal. Instead, you can just return false at the first difference you see. And then with views (as we use ~everywhere nowadays), the cloning the old value isn't expensive, as it's just a pointer under the hood. Updates #12614 Change-Id: I7b08616b8a09b3ade454bb5e0ac5672086fe8aec Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2025-10-02feature/featuretags: add features for c2n, peerapi, advertise/use ↵Brad Fitzpatrick1-0/+4
routes/exit nodes Saves 262 KB so far. I'm sure I missed some places, but shotizam says these were the low hanging fruit. Updates #12614 Change-Id: Ia31c01b454f627e6d0470229aae4e19d615e45e3 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2025-10-01net/netmon: remove usage of direct callbacks from netmon (#17292)Claus Lensbøl5-8/+25
The callback itself is not removed as it is used in other repos, making it simpler for those to slowly transition to the eventbus. Updates #15160 Signed-off-by: Claus Lensbøl <claus@tailscale.com>
2025-09-30feature/featuretags: add option to turn off DNSBrad Fitzpatrick7-2/+74
Saves 328 KB (2.5%) off the minimal binary. For IoT devices that don't need MagicDNS (e.g. they don't make outbound connections), this provides a knob to disable all the DNS functionality. Rather than a massive refactor today, this uses constant false values as a deadcode sledgehammer, guided by shotizam to find the largest DNS functions which survived deadcode. A future refactor could make it so that the net/dns/resolver and publicdns packages don't even show up in the import graph (along with their imports) but really it's already pretty good looking with just these consts, so it's not at the top of my list to refactor it more soon. Also do the same in a few places with the ACME (cert) functionality, as I saw those while searching for DNS stuff. Updates #12614 Change-Id: I8e459f595c2fde68ca16503ff61c8ab339871f97 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2025-09-29net/dns/resolver: fix data race in testBrad Fitzpatrick2-33/+21
Fixes #17339 Change-Id: I486d2a0e0931d701923c1e0f8efbda99510ab19b Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2025-09-28util/backoff: rename logtail/backoff package to util/backoffBrad Fitzpatrick1-1/+1
It has nothing to do with logtail and is confusing named like that. Updates #cleanup Updates #17323 Change-Id: Idd34587ba186a2416725f72ffc4c5778b0b9db4a Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2025-09-24net/dns, feature/featuretags: make NetworkManager, systemd-resolved, and ↵Brad Fitzpatrick4-108/+173
DBus modular Saves 360 KB (19951800 => 19591352 on linux/amd64 --extra-small --box binary) Updates #12614 Updates #17206 Change-Id: Iafd5b2536dd735111b447546cba335a7a64379ed Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2025-09-19feature/linuxdnsfight: move inotify watching of /etc/resolv.conf out to a ↵Brad Fitzpatrick4-175/+68
feature tsnet apps in particular never use the Linux DNS OSManagers, so they don't need DBus, etc. I started to pull that all out into separate features so tsnet doesn't need to bring in DBus, but hit this first. Here you can see that tsnet (and the k8s-operator) no longer pulls in inotify. Updates #17206 Change-Id: I7af0f391f60c5e7dbeed7a080346f83262346591 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2025-09-17net/dns/recursive: remove recursive DNS resolverBrad Fitzpatrick2-1364/+0
It doesn't really pull its weight: it adds 577 KB to the binary and is rarely useful. Also, we now have static IPs and other connectivity paths coming soon enough. Updates #5853 Updates #1278 Updates tailscale/corp#32168 Change-Id: If336fed00a9c9ae9745419e6d81f7de6da6f7275 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2025-09-17net/dns: don't timeout if inotify sends multiple eventsAlex Chan1-2/+7
This fixes a flaky test which has been occasionally timing out in CI. In particular, this test times out if `watchFile` receives multiple notifications from inotify before we cancel the test context. We block processing the second notification, because we've stopped listening to the `callbackDone` channel. This patch changes the test so we only send on the first notification. Testing this locally with `stress` confirms that the test is no longer flaky. Fixes #17172 Updates #14699 Signed-off-by: Alex Chan <alexc@tailscale.com>
2025-09-16health,ipn/ipnlocal: introduce eventbus in heath.Tracker (#17085)Claus Lensbøl4-13/+13
The Tracker was using direct callbacks to ipnlocal. This PR moves those to be triggered via the eventbus. Additionally, the eventbus is now closed on exit from tailscaled explicitly, and health is now a SubSystem in tsd. Updates #15160 Signed-off-by: Claus Lensbøl <claus@tailscale.com>
2025-09-02util/syspolicy: finish plumbing policyclient, add feature/syspolicy, move ↵Brad Fitzpatrick10-14/+27
global impl This is step 4 of making syspolicy a build-time feature. This adds a policyclient.Get() accessor to return the correct implementation to use: either the real one, or the no-op one. (A third type, a static one for testing, also exists, so in general a policyclient.Client should be plumbed around and not always fetched via policyclient.Get whenever possible, especially if tests need to use alternate syspolicy) Updates #16998 Updates #12614 Change-Id: Iaf19670744a596d5918acfa744f5db4564272978 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2025-09-02util/syspolicy/{setting,ptype}: move PreferenceOption and Visibility to new ↵Brad Fitzpatrick1-2/+2
leaf package Step 3 in the series. See earlier cc532efc2000 and d05e6dc09e. This step moves some types into a new leaf "ptype" package out of the big "settings" package. The policyclient.Client will later get new methods to return those things (as well as Duration and Uint64, which weren't done at the time of the earlier prototype). Updates #16998 Updates #12614 Change-Id: I4d72d8079de3b5351ed602eaa72863372bd474a2 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2025-09-01util/syspolicy/policyclient: add policyclient.Client interface, start plumbingBrad Fitzpatrick1-2/+2
This is step 2 of ~4, breaking up #14720 into reviewable chunks, with the aim to make syspolicy be a build-time configurable feature. Step 1 was #16984. In this second step, the util/syspolicy/policyclient package is added with the policyclient.Client interface. This is the interface that's always present (regardless of build tags), and is what code around the tree uses to ask syspolicy/MDM questions. There are two implementations of policyclient.Client for now: 1) NoPolicyClient, which only returns default values. 2) the unexported, temporary 'globalSyspolicy', which is implemented in terms of the global functions we wish to later eliminate. This then starts to plumb around the policyclient.Client to most callers. Future changes will plumb it more. When the last of the global func callers are gone, then we can unexport the global functions and make a proper policyclient.Client type and constructor in the syspolicy package, removing the globalSyspolicy impl out of tsd. The final change will sprinkle build tags in a few more places and lock it in with dependency tests to make sure the dependencies don't later creep back in. Updates #16998 Updates #12614 Change-Id: Ib2c93d15c15c1f2b981464099177cd492d50391c Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2025-08-31util/syspolicy/*: move syspolicy keys to new const leaf "pkey" packageBrad Fitzpatrick1-2/+3
This is step 1 of ~3, breaking up #14720 into reviewable chunks, with the aim to make syspolicy be a build-time configurable feature. In this first (very noisy) step, all the syspolicy string key constants move to a new constant-only (code-free) package. This will make future steps more reviewable, without this movement noise. There are no code or behavior changes here. The future steps of this series can be seen in #14720: removing global funcs from syspolicy resolution and using an interface that's plumbed around instead. Then adding build tags. Updates #12614 Change-Id: If73bf2c28b9c9b1a408fe868b0b6a25b03eeabd1 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2025-07-31ipn/ipnlocal, net/dns: use slices.Equal to simplify code (#16641)jishudashu1-13/+2
Signed-off-by: jishudashu <979260390@qq.com>
2025-07-21net/dns/recursive: set EDNS on queriesBrad Fitzpatrick1-0/+1
Updates tailscale/corp#30631 Change-Id: Ib88ea1bb51dd917c04f8d41bcaa6d59b9abd4f73 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2025-07-10all: detect JetKVM and specialize a handful of things for itBrad Fitzpatrick1-0/+5
Updates #16524 Change-Id: I183428de8c65d7155d82979d2d33f031c22e3331 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2025-06-18net/*: remove Windows exceptions for when Resolver.PreferGo didn't workBrad Fitzpatrick1-7/+0
Resolver.PreferGo didn't used to work on Windows. It was fixed in 2022, though. (https://github.com/golang/go/issues/33097) Updates #5161 Change-Id: I4e1aeff220ebd6adc8a14f781664fa6a2068b48c Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2025-05-28net/dns: cache dns.Config for reuse when compileConfig fails (#16059)Jonathan Nobels4-61/+76
fixes tailscale/corp#25612 We now keep track of any dns configurations which we could not compile. This gives RecompileDNSConfig a configuration to attempt to recompile and apply when the OS pokes us to indicate that the interface dns servers have changed/updated. The manager config will remain unset until we have the required information to compile it correctly which should eliminate the problematic SERVFAIL responses (especially on macOS 15). This also removes the missingUpstreamRecovery func in the forwarder which is no longer required now that we have proper error handling and recovery manager and the client. Signed-off-by: Jonathan Nobels <jonathan@tailscale.com>
2025-05-12net/dns,docs/windows/policy,util/syspolicy: register Tailscale IP addresses ↵Nick Khyl1-12/+71
in AD DNS if required by policy In this PR, we make DNS registration behavior configurable via the EnableDNSRegistration policy setting. We keep the default behavior unchanged, but allow admins to either enforce DNS registration and dynamic DNS updates for the Tailscale interface, or prevent Tailscale from modifying the settings configured in the network adapter's properties or by other means. Updates #14917 Signed-off-by: Nick Khyl <nickk@tailscale.com>
2025-05-09net/tsdial: update (*Dialer).SetRoutes() to log the size of the resulting ↵Nick Khyl1-9/+7
bart.Table Updates #12027 Signed-off-by: Nick Khyl <nickk@tailscale.com>
2025-05-09ipn/ipnlocal,net/dns/resolver: use the user dialer and routes for DNS ↵Nick Khyl1-11/+31
forwarding by default, except on iOS and Android In this PR, we make the "user-dial-routes" behavior default on all platforms except for iOS and Android. It can be disabled by setting the TS_DNS_FORWARD_USE_ROUTES envknob to 0 or false. Updates #12027 Updates #13837 Signed-off-by: Nick Khyl <nickk@tailscale.com>
2025-05-07net/dns: don't link dbus, gonotify on AndroidBrad Fitzpatrick9-6/+12
Android is Linux, but doesn't use Linux DNS managers (or D-Bus). Updates #12614 Change-Id: I487802ac74a259cd5d2480ac26f7faa17ca8d1c3 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2025-04-16net/netmon: publish events to event busDavid Anderson2-2/+9
Updates #15160 Signed-off-by: David Anderson <dave@tailscale.com>
2025-04-08net/{netx,memnet},all: add netx.DialFunc, move memnet Network implBrad Fitzpatrick1-1/+2
This adds netx.DialFunc, unifying a type we have a bazillion other places, giving it now a nice short name that's clickable in editors, etc. That highlighted that my earlier move (03b47a55c7956) of stuff from nettest into netx moved too much: it also dragged along the memnet impl, meaning all users of netx.DialFunc who just wanted netx for the type definition were instead also pulling in all of memnet. So move the memnet implementation netx.Network into memnet, a package we already had. Then use netx.DialFunc in a bunch of places. I'm sure I missed some. And plenty remain in other repos, to be updated later. Updates tailscale/corp#27636 Change-Id: I7296cd4591218e8624e214f8c70dab05fb884e95 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2025-04-02net/dns: add Plan 9 supportBrad Fitzpatrick4-2/+269
This requires the rsc/plan9 ndb DNS changes for now: https://9fans.topicbox.com/groups/9fans/T9c9d81b5801a0820/ndb-suffix-specific-dns-changes https://github.com/rsc/plan9/commit/e8c148ff092a5780d04aa2fd4a07a5732207b698 https://github.com/rsc/plan9/commit/1d0642ae493bf5ce798a6aa64a745bc6316baa11 Updates #5794 Change-Id: I0e242c1fe7bb4404e23604e03a31f89f0d18e70d Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2025-03-27net/dns: add debug envknob to enable dual stack MagicDNSBrad Fitzpatrick2-5/+24
Updates #15404 Change-Id: Ic754cc54113b1660b7071b40babb9d3c0e25b2e1 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2025-03-19net/dns: expose a function for recompiling the DNS configuration (#15346)Jonathan Nobels1-8/+28
updates tailscale/corp#27145 We require a means to trigger a recompilation of the DNS configuration to pick up new nameservers for platforms where we blend the interface nameservers from the OS into our DNS config. Notably, on Darwin, the only API we have at our disposal will, in rare instances, return a transient error when querying the interface nameservers on a link change if they have not been set when we get the AF_ROUTE messages for the link update. There's a corresponding change in corp for Darwin clients, to track the interface namservers during NEPathMonitor events, and call this when the nameservers change. This will also fix the slightly more obscure bug of changing nameservers while tailscaled is running. That change can now be reflected in magicDNS without having to stop the client. Signed-off-by: Jonathan Nobels <jonathan@tailscale.com>
2025-02-11net/dns: update to illarion/gonotify/v3 to fix a panicAnton1-23/+11
Fixes #14699 Signed-off-by: Anton <anton@tailscale.com>
2025-02-11net/dns: add a simple test for resolv.conf inotify watcherAnton2-13/+75
Updates #14699 Signed-off-by: Anton <anton@tailscale.com>
2025-01-16net/dns: only populate OSConfig.Hosts when MagicDNS is enabledAaron Klotz2-1/+73
Previously we were doing this unconditionally. Updates #14428 Signed-off-by: Aaron Klotz <aaron@tailscale.com>
2025-01-14net/dns: ensure the Windows configurator does not touch the hosts file ↵Aaron Klotz2-14/+102
unless the configuration actually changed We build up maps of both the existing MagicDNS configuration in hosts and the desired MagicDNS configuration, compare the two, and only write out a new one if there are changes. The comparison doesn't need to be perfect, as the occasional false-positive is fine, but this should greatly reduce rewrites of the hosts file. I also changed the hosts updating code to remove the CRLF/LF conversion stuff, and use Fprintf instead of Frintln to let us write those inline. Updates #14428 Signed-off-by: Aaron Klotz <aaron@tailscale.com>
2025-01-13all: use Go 1.21's binary.NativeEndianBrad Fitzpatrick1-2/+2
We still use josharian/native (hi @josharian!) via netlink, but I also sent https://github.com/mdlayher/netlink/pull/220 Updates #8632 Change-Id: I2eedcb7facb36ec894aee7f152c8a1f56d7fc8ba Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2025-01-09all: illumos/solaris userspace only supportNahum Shalman3-2/+16
Updates #14565 Change-Id: I743148144938794db0a224873ce76c10dbe6fa5f Signed-off-by: Nahum Shalman <nahamu@gmail.com>
2025-01-03util/slicesx: add MapKeys and MapValues from golang.org/x/exp/mapsBrad Fitzpatrick1-2/+2
Importing the ~deprecated golang.org/x/exp/maps as "xmaps" to not shadow the std "maps" was getting ugly. And using slices.Collect on an iterator is verbose & allocates more. So copy (x)maps.Keys+Values into our slicesx package instead. Updates #cleanup Updates #12912 Updates #14514 (pulled out of that change) Change-Id: I5e68d12729934de93cf4a9cd87c367645f86123a Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-10-28net/dns: fix blank lines being added to resolv.conf on OpenBSD (#13928)Renato Aguiar1-1/+2
During resolv.conf update, old 'search' lines are cleared but '\n' is not deleted, leaving behind a new blank line on every update. This adds 's' flag to regexp, so '\n' is included in the match and deleted when old lines are cleared. Also, insert missing `\n` when updated 'search' line is appended to resolv.conf. Signed-off-by: Renato Aguiar <renato@renatoaguiar.net>
2024-10-24net/dns/resolver: fix test flakeAndrew Dunham1-7/+11
Updates #13902 Signed-off-by: Andrew Dunham <andrew@du.nham.ca> Change-Id: Ib2def19caad17367e9a31786ac969278e65f51c6
2024-10-21health: remove SysDNSOS, add two Warnables for read+set system DNS config ↵Andrea Gottardo2-6/+31
(#13874)
2024-10-11net/dns/resolver: add tests for using a forwarder with multiple upstream ↵Nick Khyl1-45/+190
resolvers If multiple upstream DNS servers are available, quad-100 sends requests to all of them and forwards the first successful response, if any. If no successful responses are received, it propagates the first failure from any of them. This PR adds some test coverage for these scenarios. Updates #13571 Signed-off-by: Nick Khyl <nickk@tailscale.com>
2024-10-11net/dns/resolver: update (*forwarder).forwardWithDestChan to always return ↵Nick Hill3-16/+15
an error unless it sends a response to responseChan We currently have two executions paths where (*forwarder).forwardWithDestChan returns nil, rather than an error, without sending a DNS response to responseChan. These paths are accompanied by a comment that reads: // Returning an error will cause an internal retry, there is // nothing we can do if parsing failed. Just drop the packet. But it is not (or no longer longer) accurate: returning an error from forwardWithDestChan does not currently cause a retry. Moreover, although these paths are currently unreachable due to implementation details, if (*forwarder).forwardWithDestChan were to return nil without sending a response to responseChan, it would cause a deadlock at one call site and a panic at another. Therefore, we update (*forwarder).forwardWithDestChan to return errors in those two paths and remove comments that were no longer accurate and misleading. Updates #cleanup Updates #13571 Signed-off-by: Nick Hill <mykola.khyl@gmail.com>