aboutsummaryrefslogtreecommitdiffstats
path: root/library/cpp/coroutine/dns
diff options
context:
space:
mode:
authorqrort <qrort@yandex-team.com>2022-11-30 23:47:12 +0300
committerqrort <qrort@yandex-team.com>2022-11-30 23:47:12 +0300
commit22f8ae0e3f5d68b92aecccdf96c1d841a0334311 (patch)
treebffa27765faf54126ad44bcafa89fadecb7a73d7 /library/cpp/coroutine/dns
parent332b99e2173f0425444abb759eebcb2fafaa9209 (diff)
downloadydb-22f8ae0e3f5d68b92aecccdf96c1d841a0334311.tar.gz
validate canons without yatest_common
Diffstat (limited to 'library/cpp/coroutine/dns')
-rw-r--r--library/cpp/coroutine/dns/async.cpp149
-rw-r--r--library/cpp/coroutine/dns/async.h32
-rw-r--r--library/cpp/coroutine/dns/cache.cpp131
-rw-r--r--library/cpp/coroutine/dns/cache.h54
-rw-r--r--library/cpp/coroutine/dns/coro.cpp225
-rw-r--r--library/cpp/coroutine/dns/coro.h22
-rw-r--r--library/cpp/coroutine/dns/helpers.cpp148
-rw-r--r--library/cpp/coroutine/dns/helpers.h34
-rw-r--r--library/cpp/coroutine/dns/iface.h75
9 files changed, 870 insertions, 0 deletions
diff --git a/library/cpp/coroutine/dns/async.cpp b/library/cpp/coroutine/dns/async.cpp
new file mode 100644
index 0000000000..be35135828
--- /dev/null
+++ b/library/cpp/coroutine/dns/async.cpp
@@ -0,0 +1,149 @@
+#include "async.h"
+
+#include <util/generic/singleton.h>
+#include <util/generic/vector.h>
+
+#include <contrib/libs/c-ares/include/ares.h>
+
+using namespace NAsyncDns;
+
+namespace {
+ struct TAresError: public TDnsError {
+ inline TAresError(int code) {
+ (*this) << ares_strerror(code);
+ }
+ };
+
+ struct TAresInit {
+ inline TAresInit() {
+ const int code = ares_library_init(ARES_LIB_INIT_ALL);
+
+ if (code) {
+ ythrow TAresError(code) << "can not init ares engine";
+ }
+ }
+
+ inline ~TAresInit() {
+ ares_library_cleanup();
+ }
+
+ static inline void InitOnce() {
+ Singleton<TAresInit>();
+ }
+ };
+}
+
+class TAsyncDns::TImpl {
+public:
+ inline TImpl(IPoller* poller, const TOptions& o)
+ : P_(poller)
+ {
+ TAresInit::InitOnce();
+
+ ares_options opts;
+
+ Zero(opts);
+
+ int optflags = 0;
+
+ optflags |= ARES_OPT_FLAGS;
+ opts.flags = ARES_FLAG_STAYOPEN;
+
+ optflags |= ARES_OPT_TIMEOUTMS;
+ opts.timeout = o.TimeOut.MilliSeconds();
+
+ optflags |= ARES_OPT_TRIES;
+ opts.tries = o.Retries;
+
+ optflags |= ARES_OPT_SOCK_STATE_CB;
+ opts.sock_state_cb = (decltype(opts.sock_state_cb))StateCb;
+ static_assert(sizeof(opts.sock_state_cb) == sizeof(&StateCb), "Inconsistent socket state size");
+ opts.sock_state_cb_data = this;
+
+ const int code = ares_init_options(&H_, &opts, optflags);
+
+ if (code) {
+ ythrow TAresError(code) << "can not init ares channel";
+ }
+ }
+
+ inline ~TImpl() {
+ ares_destroy(H_);
+ }
+
+ inline TDuration Timeout() {
+ struct timeval tv;
+ Zero(tv);
+
+ ares_timeout(H_, nullptr, &tv);
+
+ return TDuration(tv);
+ }
+
+ template <class T>
+ inline void ProcessSocket(T s) {
+ ares_process_fd(H_, s, s);
+ }
+
+ inline void ProcessNone() {
+ ProcessSocket(ARES_SOCKET_BAD);
+ }
+
+ inline void AsyncResolve(const TNameRequest& req) {
+ ares_gethostbyname(H_, req.Addr, req.Family, AsyncResolveHostCb, req.CB);
+ }
+
+private:
+ static void StateCb(void* arg, int s, int read, int write) {
+ ((TImpl*)arg)->P_->OnStateChange((SOCKET)s, (bool)read, (bool)write);
+ }
+
+ static void AsyncResolveHostCb(void* arg, int status, int timeouts, hostent* he) {
+ const IHostResult::TResult res = {
+ status, timeouts, he};
+
+ ((IHostResult*)arg)->OnComplete(res);
+ }
+
+private:
+ IPoller* P_;
+ ares_channel H_;
+};
+
+void NAsyncDns::CheckAsyncStatus(int status) {
+ if (status) {
+ ythrow TAresError(status);
+ }
+}
+
+void NAsyncDns::CheckPartialAsyncStatus(int status) {
+ if (status == ARES_ENODATA) {
+ return;
+ }
+
+ CheckAsyncStatus(status);
+}
+
+TAsyncDns::TAsyncDns(IPoller* poller, const TOptions& opts)
+ : I_(new TImpl(poller, opts))
+{
+}
+
+TAsyncDns::~TAsyncDns() {
+}
+
+void TAsyncDns::AsyncResolve(const TNameRequest& req) {
+ I_->AsyncResolve(req);
+}
+
+TDuration TAsyncDns::Timeout() {
+ return I_->Timeout();
+}
+
+void TAsyncDns::ProcessSocket(SOCKET s) {
+ I_->ProcessSocket(s);
+}
+
+void TAsyncDns::ProcessNone() {
+ I_->ProcessNone();
+}
diff --git a/library/cpp/coroutine/dns/async.h b/library/cpp/coroutine/dns/async.h
new file mode 100644
index 0000000000..1ec26bc91d
--- /dev/null
+++ b/library/cpp/coroutine/dns/async.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include "iface.h"
+
+#include <util/network/socket.h>
+#include <util/datetime/base.h>
+#include <util/generic/ptr.h>
+
+namespace NAsyncDns {
+ struct IPoller {
+ virtual void OnStateChange(SOCKET s, bool read, bool write) = 0;
+ };
+
+ class TAsyncDns {
+ public:
+ TAsyncDns(IPoller* poller, const TOptions& opts = TOptions());
+ ~TAsyncDns();
+
+ void AsyncResolve(const TNameRequest& req);
+
+ TDuration Timeout();
+ void ProcessSocket(SOCKET s);
+ void ProcessNone();
+
+ private:
+ class TImpl;
+ THolder<TImpl> I_;
+ };
+
+ void CheckAsyncStatus(int status);
+ void CheckPartialAsyncStatus(int status);
+}
diff --git a/library/cpp/coroutine/dns/cache.cpp b/library/cpp/coroutine/dns/cache.cpp
new file mode 100644
index 0000000000..69910a7960
--- /dev/null
+++ b/library/cpp/coroutine/dns/cache.cpp
@@ -0,0 +1,131 @@
+#include "cache.h"
+
+#include "async.h"
+#include "coro.h"
+#include "helpers.h"
+
+#include <library/cpp/cache/cache.h>
+
+#include <library/cpp/coroutine/engine/impl.h>
+#include <library/cpp/coroutine/engine/events.h>
+
+using namespace NAddr;
+using namespace NAsyncDns;
+
+class TContDnsCache::TImpl {
+ using TKey = std::pair<TString, ui16>;
+
+ enum EResolveState {
+ RS_NOT_RESOLVED,
+ RS_IN_PROGRESS,
+ RS_RESOLVED,
+ RS_FAILED,
+ RS_EXPIRED
+ };
+
+ struct TEntry: public TSimpleRefCount<TEntry> {
+ TAddrs Addrs_;
+ TInstant ResolveTimestamp_;
+ EResolveState State_;
+ TContSimpleEvent ResolvedEvent_;
+ TString LastResolveError_;
+
+ TEntry(TContExecutor* e)
+ : State_(RS_NOT_RESOLVED)
+ , ResolvedEvent_(e)
+ {
+ }
+ };
+
+ using TEntryRef = TIntrusivePtr<TEntry>;
+
+public:
+ inline TImpl(TContExecutor* e, const TCacheOptions& opts)
+ : Executor_(e)
+ , Opts_(opts)
+ , Cache_(opts.MaxSize)
+ {
+ }
+
+ inline ~TImpl() {
+ }
+
+ void LookupOrResolve(TContResolver& resolver, const TString& addr, ui16 port, TAddrs& result) {
+ TKey cacheKey(addr, port);
+
+ TEntryRef e;
+ auto iter = Cache_.Find(cacheKey);
+ if (iter == Cache_.End()) {
+ e = MakeIntrusive<TEntry>(Executor_);
+ Cache_.Insert(cacheKey, e);
+ } else {
+ e = iter.Value();
+ }
+
+ while (true) {
+ switch (e->State_) {
+ case RS_NOT_RESOLVED:
+ case RS_EXPIRED:
+ e->State_ = RS_IN_PROGRESS;
+ try {
+ ResolveAddr(resolver, addr, port, result);
+ } catch (TDnsError& err) {
+ e->ResolveTimestamp_ = TInstant::Now();
+ e->LastResolveError_ = err.AsStrBuf();
+ e->State_ = RS_FAILED;
+ e->ResolvedEvent_.BroadCast();
+ throw;
+ } catch (...) {
+ // errors not related to DNS
+ e->State_ = RS_NOT_RESOLVED;
+ e->ResolvedEvent_.BroadCast();
+ throw;
+ }
+ e->ResolveTimestamp_ = TInstant::Now();
+ e->Addrs_ = result;
+ e->State_ = RS_RESOLVED;
+ e->ResolvedEvent_.BroadCast();
+ return;
+ case RS_IN_PROGRESS:
+ e->ResolvedEvent_.WaitI();
+ continue;
+ case RS_RESOLVED:
+ if (e->ResolveTimestamp_ + Opts_.EntryLifetime < TInstant::Now()) {
+ e->State_ = RS_EXPIRED;
+ continue; // try and resolve again
+ }
+ result = e->Addrs_;
+ return;
+ case RS_FAILED:
+ if (e->ResolveTimestamp_ + Opts_.NotFoundLifetime < TInstant::Now()) {
+ e->State_ = RS_EXPIRED;
+ continue; // try and resolve again
+ }
+ ythrow TDnsError() << e->LastResolveError_;
+ default:
+ Y_FAIL("Bad state, shoult not get here");
+ };
+ }
+ }
+
+private:
+ TContExecutor* Executor_;
+ const TCacheOptions Opts_;
+ TLRUCache<TKey, TEntryRef> Cache_;
+};
+
+TContDnsCache::TContDnsCache(TContExecutor* e, const NAsyncDns::TCacheOptions& opts)
+ : I_(MakeHolder<TImpl>(e, opts))
+{
+}
+
+TContDnsCache::~TContDnsCache() {
+}
+
+void TContDnsCache::LookupOrResolve(TContResolver& resolver, const TString& addr, ui16 port, TVector<NAddr::IRemoteAddrRef>& result) {
+ return I_->LookupOrResolve(resolver, addr, port, result);
+}
+
+void TContDnsCache::LookupOrResolve(TContResolver& resolver, const TString& addr, TVector<NAddr::IRemoteAddrRef>& result) {
+ LookupOrResolve(resolver, addr, 80, result);
+}
diff --git a/library/cpp/coroutine/dns/cache.h b/library/cpp/coroutine/dns/cache.h
new file mode 100644
index 0000000000..7c358ce591
--- /dev/null
+++ b/library/cpp/coroutine/dns/cache.h
@@ -0,0 +1,54 @@
+#pragma once
+
+#include "iface.h"
+
+#include <util/generic/ptr.h>
+#include <util/generic/vector.h>
+#include <util/network/address.h>
+
+class TContExecutor;
+
+namespace NAsyncDns {
+ class TContResolver;
+
+ struct TCacheOptions {
+ inline TCacheOptions() {
+ }
+
+ inline TCacheOptions& SetEntryLifetime(const TDuration& val) noexcept {
+ EntryLifetime = val;
+
+ return *this;
+ }
+
+ inline TCacheOptions& SetNotFoundLifetime(const TDuration& val) noexcept {
+ NotFoundLifetime = val;
+
+ return *this;
+ }
+
+ inline TCacheOptions& SetMaxSize(size_t val) noexcept {
+ MaxSize = val;
+
+ return *this;
+ }
+
+ size_t MaxSize = 512;
+ TDuration EntryLifetime = TDuration::Seconds(1800);
+ TDuration NotFoundLifetime = TDuration::Seconds(1);
+ };
+
+ // MT-unsafe LRU DNS cache
+ class TContDnsCache {
+ public:
+ TContDnsCache(TContExecutor* e, const TCacheOptions& opts = {});
+ ~TContDnsCache();
+
+ void LookupOrResolve(TContResolver& resolver, const TString& addr, ui16 port, TVector<NAddr::IRemoteAddrRef>& result);
+ void LookupOrResolve(TContResolver& resolver, const TString& addr, TVector<NAddr::IRemoteAddrRef>& result);
+
+ private:
+ class TImpl;
+ THolder<TImpl> I_;
+ };
+}
diff --git a/library/cpp/coroutine/dns/coro.cpp b/library/cpp/coroutine/dns/coro.cpp
new file mode 100644
index 0000000000..9d6a89f6e7
--- /dev/null
+++ b/library/cpp/coroutine/dns/coro.cpp
@@ -0,0 +1,225 @@
+#include "coro.h"
+
+#include "async.h"
+
+#include <library/cpp/coroutine/engine/events.h>
+#include <library/cpp/coroutine/engine/impl.h>
+
+#include <util/generic/vector.h>
+#include <util/memory/smallobj.h>
+
+using namespace NAsyncDns;
+
+class TContResolver::TImpl: public IPoller {
+ struct TPollEvent:
+ public NCoro::IPollEvent,
+ public TIntrusiveListItem<TPollEvent>,
+ public TObjectFromPool<TPollEvent>
+ {
+ inline TPollEvent(TImpl* parent, SOCKET s, int what)
+ : IPollEvent(s, what)
+ , P(parent)
+ {
+ P->E_->Poller()->Schedule(this);
+ }
+
+ inline ~TPollEvent() override {
+ P->E_->Poller()->Remove(this);
+ }
+
+ void OnPollEvent(int) noexcept override {
+ P->D_.ProcessSocket(Fd());
+ }
+
+ TImpl* P;
+ };
+
+ typedef TAutoPtr<TPollEvent> TEventRef;
+
+ struct TOneShotEvent {
+ inline TOneShotEvent(TContExecutor* e) noexcept
+ : E(e)
+ , S(false)
+ {
+ }
+
+ inline void Signal() noexcept {
+ S = true;
+ E.Signal();
+ }
+
+ inline void Wait() noexcept {
+ if (S) {
+ return;
+ }
+
+ E.WaitI();
+ }
+
+ TContSimpleEvent E;
+ bool S;
+ };
+
+ struct TContSemaphore {
+ inline TContSemaphore(TContExecutor* e, size_t num) noexcept
+ : E(e)
+ , N(num)
+ {
+ }
+
+ inline void Acquire() noexcept {
+ while (!N) {
+ E.WaitI();
+ }
+
+ --N;
+ }
+
+ inline void Release() noexcept {
+ ++N;
+ E.Signal();
+ }
+
+ TContSimpleEvent E;
+ size_t N;
+ };
+
+ struct TRequest: public TIntrusiveListItem<TRequest>, public TOneShotEvent, public IHostResult {
+ inline TRequest(const TNameRequest* req, TContExecutor* e)
+ : TOneShotEvent(e)
+ , Req(req)
+ {
+ }
+
+ void OnComplete(const TResult& res) override {
+ Req->CB->OnComplete(res);
+ Signal();
+ }
+
+ const TNameRequest* Req;
+ };
+
+public:
+ inline TImpl(TContExecutor* e, const TOptions& opts)
+ : P_(TDefaultAllocator::Instance())
+ , E_(e)
+ , RateLimit_(E_, opts.MaxRequests)
+ , D_(this, opts)
+ , DC_(nullptr)
+ {
+ }
+
+ inline ~TImpl() {
+ Y_VERIFY(DC_ == nullptr, "shit happens");
+ }
+
+ inline void Resolve(const TNameRequest& hreq) {
+ TGuard<TContSemaphore> g(RateLimit_);
+
+ if (hreq.Family == AF_UNSPEC) {
+ const TNameRequest hreq1(hreq.Copy(AF_INET));
+ TRequest req1(&hreq1, E_);
+
+ const TNameRequest hreq2(hreq.Copy(AF_INET6));
+ TRequest req2(&hreq2, E_);
+
+ Schedule(&req1);
+ Schedule(&req2);
+
+ req1.Wait();
+ req2.Wait();
+ } else {
+ TRequest req(&hreq, E_);
+
+ Schedule(&req);
+
+ req.Wait();
+ }
+
+ if (A_.Empty() && DC_) {
+ DC_->ReSchedule();
+ }
+ }
+
+private:
+ inline void Schedule(TRequest* req) {
+ D_.AsyncResolve(req->Req->Copy(req));
+ A_.PushBack(req);
+ ResolveCont()->ReSchedule();
+ }
+
+ inline TCont* ResolveCont() {
+ if (!DC_) {
+ DC_ = E_->Create<TImpl, &TImpl::RunAsyncDns>(this, "async_dns");
+ }
+
+ return DC_;
+ }
+
+ void RunAsyncDns(TCont*) {
+ while (!A_.Empty()) {
+ Y_VERIFY(!I_.Empty(), "shit happens");
+
+ const TDuration tout = D_.Timeout();
+
+ if (E_->Running()->SleepT(Max(tout, TDuration::MilliSeconds(10))) == ETIMEDOUT) {
+ D_.ProcessNone();
+ }
+
+ DC_->Yield();
+ }
+
+ DC_ = nullptr;
+ }
+
+ void OnStateChange(SOCKET s, bool read, bool write) noexcept override {
+ static const ui16 WHAT[] = {
+ 0, CONT_POLL_READ, CONT_POLL_WRITE, CONT_POLL_READ | CONT_POLL_WRITE};
+
+ const int what = WHAT[((size_t)read) | (((size_t)write) << 1)];
+
+ TEventRef& ev = S_.Get(s);
+
+ if (!ev) {
+ ev.Reset(new (&P_) TPollEvent(this, s, what));
+ I_.PushBack(ev.Get());
+ } else {
+ if (what) {
+ if (ev->What() != what) {
+ //may optimize
+ ev.Reset(new (&P_) TPollEvent(this, s, what));
+ I_.PushBack(ev.Get());
+ }
+ } else {
+ ev.Destroy();
+ //auto unlink
+ }
+ }
+
+ if (DC_) {
+ DC_->ReSchedule();
+ }
+ }
+
+private:
+ TSocketMap<TEventRef> S_;
+ TPollEvent::TPool P_;
+ TContExecutor* E_;
+ TContSemaphore RateLimit_;
+ TAsyncDns D_;
+ TIntrusiveList<TPollEvent> I_;
+ TIntrusiveList<TRequest> A_;
+ TCont* DC_;
+};
+
+TContResolver::TContResolver(TContExecutor* e, const TOptions& opts)
+ : I_(new TImpl(e, opts))
+{
+}
+
+TContResolver::~TContResolver() {
+}
+
+void TContResolver::Resolve(const TNameRequest& req) {
+ I_->Resolve(req);
+}
diff --git a/library/cpp/coroutine/dns/coro.h b/library/cpp/coroutine/dns/coro.h
new file mode 100644
index 0000000000..62a01891b8
--- /dev/null
+++ b/library/cpp/coroutine/dns/coro.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include "iface.h"
+
+#include <util/generic/ptr.h>
+#include <util/generic/ylimits.h>
+
+class TContExecutor;
+
+namespace NAsyncDns {
+ class TContResolver {
+ public:
+ TContResolver(TContExecutor* e, const TOptions& opts = TOptions());
+ ~TContResolver();
+
+ void Resolve(const TNameRequest& hreq);
+
+ private:
+ class TImpl;
+ THolder<TImpl> I_;
+ };
+}
diff --git a/library/cpp/coroutine/dns/helpers.cpp b/library/cpp/coroutine/dns/helpers.cpp
new file mode 100644
index 0000000000..21d17b5d67
--- /dev/null
+++ b/library/cpp/coroutine/dns/helpers.cpp
@@ -0,0 +1,148 @@
+#include "helpers.h"
+#include "coro.h"
+#include "async.h"
+#include "cache.h"
+
+#include <util/digest/city.h>
+#include <util/generic/hash_set.h>
+
+using namespace NAddr;
+using namespace NAsyncDns;
+
+namespace {
+ typedef ui64 TAddrHash;
+
+ inline TAddrHash Hash(const IRemoteAddrRef& addr) {
+ return CityHash64((const char*)addr->Addr(), addr->Len());
+ }
+
+ inline IRemoteAddrRef ConstructIP4(void* data, ui16 port) {
+ return new TIPv4Addr(TIpAddress(*(ui32*)data, port));
+ }
+
+ inline IRemoteAddrRef ConstructIP6(void* data, ui16 port) {
+ sockaddr_in6 res;
+
+ Zero(res);
+
+ res.sin6_family = AF_INET6;
+ res.sin6_port = HostToInet(port);
+ memcpy(&res.sin6_addr.s6_addr, data, sizeof(res.sin6_addr.s6_addr));
+
+ return new TIPv6Addr(res);
+ }
+
+ inline IRemoteAddrRef Construct(const hostent* h, void* data, ui16 port) {
+ switch (h->h_addrtype) {
+ case AF_INET:
+ return ConstructIP4(data, port);
+
+ case AF_INET6:
+ return ConstructIP6(data, port);
+ }
+
+ //real shit happens
+ abort();
+ }
+
+ template <class It, class T>
+ static bool FindByHash(It b, It e, T t) {
+ while (b != e) {
+ if (Hash(*b) == t) {
+ return true;
+ }
+
+ ++b;
+ }
+
+ return false;
+ }
+
+ inline size_t LstLen(char** lst) noexcept {
+ size_t ret = 0;
+
+ while (*lst) {
+ ++ret;
+ ++lst;
+ }
+
+ return ret;
+ }
+}
+
+void TResolveAddr::OnComplete(const TResult& r) {
+ const hostent* h = r.Result;
+
+ if (!h) {
+ Status.push_back(r.Status);
+
+ return;
+ }
+
+ char** lst = h->h_addr_list;
+
+ typedef THashSet<TAddrHash> THashes;
+ TAutoPtr<THashes> hashes;
+
+ if ((Result.size() + LstLen(lst)) > 8) {
+ hashes.Reset(new THashes());
+
+ for (const auto& it : Result) {
+ hashes->insert(Hash(it));
+ }
+ }
+
+ while (*lst) {
+ IRemoteAddrRef addr = Construct(h, *lst, Port);
+
+ if (!hashes) {
+ if (!FindByHash(Result.begin(), Result.end(), Hash(addr))) {
+ Result.push_back(addr);
+ }
+ } else {
+ const TAddrHash h = Hash(addr);
+
+ if (hashes->find(h) == hashes->end()) {
+ hashes->insert(h);
+ Result.push_back(addr);
+ }
+ }
+
+ ++lst;
+ }
+}
+
+void NAsyncDns::ResolveAddr(TContResolver& resolver, const TString& host, ui16 port, TAddrs& result) {
+ TResolveAddr cb(port);
+
+ resolver.Resolve(TNameRequest(host.data(), AF_UNSPEC, &cb));
+
+ if (cb.Result) {
+ for (auto status : cb.Status) {
+ //we have some results, so skip empty responses for aaaa requests
+ CheckPartialAsyncStatus(status);
+ }
+ } else {
+ for (auto status : cb.Status) {
+ CheckAsyncStatus(status);
+ }
+ }
+
+ cb.Result.swap(result);
+}
+
+void NAsyncDns::ResolveAddr(TContResolver& resolver, const TString& addr, TAddrs& result) {
+ ResolveAddr(resolver, addr, 80, result);
+}
+
+void NAsyncDns::ResolveAddr(TContResolver& resolver, const TString& host, ui16 port, TAddrs& result, TContDnsCache* cache) {
+ if (cache) {
+ cache->LookupOrResolve(resolver, host, port, result);
+ } else {
+ ResolveAddr(resolver, host, port, result);
+ }
+}
+
+void NAsyncDns::ResolveAddr(TContResolver& resolver, const TString& addr, TAddrs& result, TContDnsCache* cache) {
+ ResolveAddr(resolver, addr, 80, result, cache);
+}
diff --git a/library/cpp/coroutine/dns/helpers.h b/library/cpp/coroutine/dns/helpers.h
new file mode 100644
index 0000000000..5f3cc8676f
--- /dev/null
+++ b/library/cpp/coroutine/dns/helpers.h
@@ -0,0 +1,34 @@
+#pragma once
+
+#include "iface.h"
+
+#include <util/network/address.h>
+#include <util/generic/vector.h>
+
+namespace NAsyncDns {
+ class TContResolver;
+ class TContDnsCache;
+ using NAddr::IRemoteAddrRef;
+
+ typedef TVector<IRemoteAddrRef> TAddrs;
+
+ struct TResolveAddr: public IHostResult {
+ inline TResolveAddr(ui16 port)
+ : Port(port)
+ , Status(0)
+ {
+ }
+
+ void OnComplete(const TResult& result) override;
+
+ TAddrs Result;
+ ui16 Port;
+ TVector<int> Status;
+ };
+
+ //resolve addr(host + port), like TNetworkAddress
+ void ResolveAddr(TContResolver& resolver, const TString& addr, TAddrs& result);
+ void ResolveAddr(TContResolver& resolver, const TString& host, ui16 port, TAddrs& result);
+ void ResolveAddr(TContResolver& resolver, const TString& addr, TAddrs& result, TContDnsCache* cache);
+ void ResolveAddr(TContResolver& resolver, const TString& host, ui16 port, TAddrs& result, TContDnsCache* cache);
+}
diff --git a/library/cpp/coroutine/dns/iface.h b/library/cpp/coroutine/dns/iface.h
new file mode 100644
index 0000000000..0310f48f8f
--- /dev/null
+++ b/library/cpp/coroutine/dns/iface.h
@@ -0,0 +1,75 @@
+#pragma once
+
+#include <util/datetime/base.h>
+#include <util/generic/yexception.h>
+
+struct hostent;
+
+namespace NAsyncDns {
+ struct TOptions {
+ inline TOptions()
+ : MaxRequests(Max())
+ , Retries(5)
+ , TimeOut(TDuration::MilliSeconds(50))
+ {
+ }
+
+ inline TOptions& SetMaxRequests(size_t val) noexcept {
+ MaxRequests = val;
+
+ return *this;
+ }
+
+ inline TOptions& SetRetries(size_t val) noexcept {
+ Retries = val;
+
+ return *this;
+ }
+
+ inline TOptions& SetTimeOut(const TDuration& val) noexcept {
+ TimeOut = val;
+
+ return *this;
+ }
+
+ size_t MaxRequests;
+ size_t Retries;
+ TDuration TimeOut;
+ };
+
+ struct IHostResult {
+ struct TResult {
+ int Status;
+ int Timeouts;
+ const hostent* Result;
+ };
+
+ virtual ~IHostResult() = default;
+
+ virtual void OnComplete(const TResult& result) = 0;
+ };
+
+ struct TNameRequest {
+ inline TNameRequest(const char* addr, int family, IHostResult* cb)
+ : Addr(addr)
+ , Family(family)
+ , CB(cb)
+ {
+ }
+
+ inline TNameRequest Copy(IHostResult* cb) const noexcept {
+ return TNameRequest(Addr, Family, cb);
+ }
+
+ inline TNameRequest Copy(int family) const noexcept {
+ return TNameRequest(Addr, family, CB);
+ }
+
+ const char* Addr;
+ int Family;
+ IHostResult* CB;
+ };
+
+ struct TDnsError: public yexception {
+ };
+}