diff --git a/libdap-chain-net b/libdap-chain-net
deleted file mode 160000
index baa8092d1c013060d6c79e35083ebd61d2eaaf32..0000000000000000000000000000000000000000
--- a/libdap-chain-net
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit baa8092d1c013060d6c79e35083ebd61d2eaaf32
diff --git a/libdap-chain-net/.gitignore b/libdap-chain-net/.gitignore
new file mode 100755
index 0000000000000000000000000000000000000000..715b2fcabce319becb23b4f67ba7b9bb4beaf7d5
--- /dev/null
+++ b/libdap-chain-net/.gitignore
@@ -0,0 +1,55 @@
+# Prerequisites
+*.d
+*.autosave
+# Object files
+*.o
+*.ko
+*.obj
+*.elf
+
+# Linker output
+*.ilk
+*.map
+*.exp
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Libraries
+*.lib
+*.a
+*.la
+*.lo
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+*.out
+*.app
+*.i*86
+*.x86_64
+*.hex
+
+# Debug files
+*.dSYM/
+*.su
+*.idb
+*.pdb
+
+# Kernel Module Compile Results
+*.mod*
+*.cmd
+.tmp_versions/
+modules.order
+Module.symvers
+Mkfile.old
+dkms.conf
+/build/
+/.project
+/.cproject
diff --git a/libdap-chain-net/CMakeLists.txt b/libdap-chain-net/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..7579c48ad2b9e4a9c62cee6e880348bd585f1b03
--- /dev/null
+++ b/libdap-chain-net/CMakeLists.txt
@@ -0,0 +1,83 @@
+cmake_minimum_required(VERSION 3.0)
+project (dap_chain_net)
+  
+
+set(DAP_CHAIN_NET_SRCS 
+		dap_chain_net.c 
+		dap_chain_net_remote.c
+		dap_chain_net_bugreport.c
+        dap_chain_node.c
+        dap_chain_node_cli.c
+        dap_chain_node_cli_cmd.c
+        dap_chain_node_cli_cmd_tx.c
+        dap_chain_node_client.c
+        dap_chain_node_remote.c
+        dap_chain_node_ping.c
+        )
+
+set(DAP_CHAIN_NET_HEADERS
+        dap_chain_net.h
+		dap_chain_net_remote.h
+		dap_chain_net_bugreport.h
+        dap_chain_node.h
+        dap_chain_node_cli.h
+        dap_chain_node_cli_cmd.h
+        dap_chain_node_cli_cmd_tx.h
+        dap_chain_node_client.h
+        dap_chain_node_remote.h
+        dap_chain_node_ping.h
+    )
+
+#if (ANDROID)
+#    set(DAP_CHAIN_NET_HEADERS ${DAP_CHAIN_NET_HEADERS}
+#        android/ifaddrs-android.h
+#    )
+#endif()
+
+set(IPUTILS_INCLUDE_DIRS
+     iputils/traceroute/
+    )
+
+if(NOT (WIN32))
+  file(GLOB IPUTILS_SRCS iputils/*.c iputils/traceroute/*.c)
+  file(GLOB IPUTILS_HEADERS iputils/*.h ${IPUTILS_INCLUDE_DIRS}*.h)
+endif()
+
+if(WIN32)
+  add_definitions ("-DUNDEBUG")
+  add_definitions ("-DNDEBUG")
+  add_definitions ("-DWIN32")
+  add_definitions ("-D_WINDOWS")
+  add_definitions ("-D__WINDOWS__")
+  add_definitions ("-D_CRT_SECURE_NO_WARNINGS")
+  add_definitions ("-D_POSIX")
+  add_definitions ("-D_POSIX_")
+  add_definitions ("-D_POSIX_THREAD_SAFE_FUNCTIONS")
+  add_compile_definitions(_GNU_SOURCE)
+  include_directories(../3rdparty/wepoll/)
+  include_directories(../3rdparty/uthash/src/)
+endif()
+
+add_library(${PROJECT_NAME} STATIC ${DAP_CHAIN_NET_SRCS} ${DAP_CHAIN_NET_HEADERS} ${IPUTILS_SRCS} ${IPUTILS_HEADERS})
+
+if(WIN32)
+  target_link_libraries(dap_chain_net dap_core dap_crypto dap_client dap_stream_ch_chain dap_stream_ch_chain_net dap_chain dap_chain_crypto dap_chain_wallet dap_chain_net_srv dap_chain_mempool dap_chain_global_db )
+endif()
+
+if(UNIX)
+    target_link_libraries(${PROJECT_NAME} dap_core dap_crypto dap_client dap_stream_ch_chain dap_stream_ch_chain_net dap_chain
+      dap_chain_crypto dap_chain_wallet dap_chain_net_srv dap_chain_net_srv_vpn dap_chain_mempool dap_chain_global_db
+      resolv
+      )
+endif()
+
+target_include_directories(${PROJECT_NAME} INTERFACE . )
+
+if (!WIN32)
+    target_include_directories(${PROJECT_NAME} PUBLIC ${IPUTILS_INCLUDE_DIRS})
+endif()
+
+
+set(${PROJECT_NAME}_DEFINITIONS CACHE INTERNAL "${PROJECT_NAME}: Definitions" FORCE)
+
+set(${PROJECT_NAME}_INCLUDE_DIRS ${PROJECT_SOURCE_DIR} CACHE INTERNAL "${PROJECT_NAME}: Include Directories" FORCE)
diff --git a/libdap-chain-net/LICENSE b/libdap-chain-net/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..94a9ed024d3859793618152ea559a168bbcbb5e2
--- /dev/null
+++ b/libdap-chain-net/LICENSE
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/libdap-chain-net/README.md b/libdap-chain-net/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..b3b20222fe48b3b606cedeb96ccd3bc5ca31a405
--- /dev/null
+++ b/libdap-chain-net/README.md
@@ -0,0 +1,2 @@
+# libdap-chain-net
+Dap ChainNetwork functions
diff --git a/libdap-chain-net/dap_chain_net.c b/libdap-chain-net/dap_chain_net.c
new file mode 100644
index 0000000000000000000000000000000000000000..d97caf8915102a2c6e835410ef1704982326cd49
--- /dev/null
+++ b/libdap-chain-net/dap_chain_net.c
@@ -0,0 +1,2051 @@
+/*
+ * Authors:
+ * Dmitriy A. Gearasimov <gerasimov.dmitriy@demlabs.net>
+ * Alexander Lysikov <alexander.lysikov@demlabs.net>
+ * DeM Labs Inc.   https://demlabs.net
+ * Kelvin Project https://github.com/kelvinblockchain
+ * Copyright  (c) 2017-2018
+ * All rights reserved.
+
+ This file is part of DAP (Deus Applications Prototypes) the open source project
+
+    DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    DAP is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with any DAP based project.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <fnmatch.h>
+
+#ifdef DAP_OS_UNIX
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#endif
+
+#ifdef WIN32
+#include <winsock2.h>
+#include <windows.h>
+#include <mswsock.h>
+#include <ws2tcpip.h>
+#include <io.h>
+#endif
+
+#include <pthread.h>
+
+#include "uthash.h"
+#include "utlist.h"
+
+#include "dap_common.h"
+#include "dap_string.h"
+#include "dap_strfuncs.h"
+#include "dap_file_utils.h"
+#include "dap_config.h"
+#include "dap_hash.h"
+#include "dap_cert.h"
+#include "dap_chain_common.h"
+#include "dap_chain_net.h"
+#include "dap_chain_node_client.h"
+#include "dap_chain_node_cli.h"
+#include "dap_chain_node_cli_cmd.h"
+#include "dap_chain_ledger.h"
+
+#include "dap_chain_global_db.h"
+#include "dap_chain_global_db_remote.h"
+
+#include "dap_stream_ch_chain_net_pkt.h"
+#include "dap_stream_ch_chain_net.h"
+#include "dap_stream_ch_chain.h"
+#include "dap_stream_ch_chain_pkt.h"
+#include "dap_stream_ch.h"
+#include "dap_stream_ch_pkt.h"
+
+#include "dap_module.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <dirent.h>
+
+#define _XOPEN_SOURCE       /* See feature_test_macros(7) */
+#define __USE_XOPEN
+#define _GNU_SOURCE
+#include <time.h>
+
+#define LOG_TAG "chain_net"
+
+#define F_DAP_CHAIN_NET_SHUTDOWN  ( 1 << 9 )
+#define F_DAP_CHAIN_NET_GO_SYNC   ( 1 << 10 )
+
+// maximum number of connections
+static size_t s_max_links_count = 5;// by default 5
+// number of required connections
+static size_t s_required_links_count = 3;// by default 3
+
+/**
+  * @struct dap_chain_net_pvt
+  * @details Private part of chain_net dap object
+  */
+typedef struct dap_chain_net_pvt{
+    pthread_t proc_tid;
+#ifndef _WIN32
+    pthread_cond_t state_proc_cond;
+#else
+    HANDLE state_proc_cond;
+#endif
+    dap_chain_node_role_t node_role;
+    uint32_t  flags;
+    time_t    last_sync;
+
+    dap_chain_node_addr_t * node_addr;
+    dap_chain_node_info_t * node_info; // Current node's info
+
+    dap_chain_node_client_t * links;
+    size_t links_count;
+
+    dap_chain_node_addr_t *links_addrs;
+    size_t links_addrs_count;
+
+    size_t addr_request_attempts;
+    bool load_mode;
+    uint8_t padding2[7];
+    char ** seed_aliases;
+
+    uint16_t gdb_sync_groups_count;
+    uint16_t gdb_sync_nodes_addrs_count;
+    char **gdb_sync_groups;
+    dap_chain_node_addr_t *gdb_sync_nodes_addrs;
+
+    uint8_t padding3[6];
+    uint16_t seed_aliases_count;
+
+    dap_chain_net_state_t state;
+    dap_chain_net_state_t state_target;
+    dap_chain_net_state_t state_new;
+} dap_chain_net_pvt_t;
+
+typedef struct dap_chain_net_item{
+    char name [DAP_CHAIN_NET_NAME_MAX];
+    dap_chain_net_id_t net_id;
+    dap_chain_net_t * chain_net;
+    UT_hash_handle hh;
+} dap_chain_net_item_t;
+
+#define PVT(a) ( (dap_chain_net_pvt_t *) (void*) a->pvt )
+#define PVT_S(a) ( (dap_chain_net_pvt_t *) (void*) a.pvt )
+
+static dap_chain_net_item_t * s_net_items = NULL;
+static dap_chain_net_item_t * s_net_items_ids = NULL;
+
+
+static const char * c_net_states[]={
+    [NET_STATE_OFFLINE] = "NET_STATE_OFFLINE",
+    [NET_STATE_LINKS_PREPARE ] = "NET_STATE_LINKS_PREPARE",
+    [NET_STATE_LINKS_CONNECTING] = "NET_STATE_LINKS_CONNECTING",
+    [NET_STATE_LINKS_ESTABLISHED]= "NET_STATE_LINKS_ESTABLISHED",
+    [NET_STATE_SYNC_GDB]= "NET_STATE_SYNC_GDB",
+    [NET_STATE_SYNC_CHAINS]= "NET_STATE_SYNC_CHAINS",
+    [NET_STATE_ADDR_REQUEST]= "NET_STATE_ADDR_REQUEST",
+    [NET_STATE_ONLINE]= "NET_STATE_ONLINE"
+};
+
+static dap_chain_net_t * s_net_new(const char * a_id, const char * a_name , const char * a_node_role);
+inline static const char * s_net_state_to_str(dap_chain_net_state_t l_state);
+static int s_net_states_proc(dap_chain_net_t * l_net);
+static void * s_net_proc_thread ( void * a_net);
+static void s_net_proc_thread_start( dap_chain_net_t * a_net );
+static void s_net_proc_kill( dap_chain_net_t * a_net );
+int s_net_load(const char * a_net_name);
+
+static void s_gbd_history_callback_notify (void * a_arg,const char a_op_code, const char * a_prefix, const char * a_group,
+                                                     const char * a_key, const void * a_value,
+                                                     const size_t a_value_len);
+static void s_chain_callback_notify(void * a_arg, dap_chain_t *a_chain, dap_chain_cell_id_t a_id);
+
+static int s_cli_net(int argc, char ** argv, void *arg_func, char **str_reply);
+
+static bool s_seed_mode = false;
+
+
+/**
+ * @brief s_net_set_go_sync
+ * @param a_net
+ * @return
+ */
+void s_net_set_go_sync(dap_chain_net_t * a_net)
+{
+    if(!a_net)
+        return;
+    dap_chain_net_start(a_net);
+}
+
+/**
+ * @brief s_net_state_to_str
+ * @param l_state
+ * @return
+ */
+inline static const char * s_net_state_to_str(dap_chain_net_state_t l_state)
+{
+    return c_net_states[l_state];
+}
+
+/**
+ * @brief dap_chain_net_state_go_to
+ * @param a_net
+ * @param a_new_state
+ */
+int dap_chain_net_state_go_to(dap_chain_net_t * a_net, dap_chain_net_state_t a_new_state)
+{
+    if (PVT(a_net)->state_new == a_new_state){
+        log_it(L_WARNING,"Already going to state %s",s_net_state_to_str(a_new_state));
+    }
+    PVT(a_net)->state_new = a_new_state;
+    return 0;
+}
+
+/**
+ * @brief s_gbd_history_callback_notify
+ * @param a_arg
+ * @param a_op_code
+ * @param a_prefix
+ * @param a_group
+ * @param a_key
+ * @param a_value
+ * @param a_value_len
+ */
+static void s_gbd_history_callback_notify (void * a_arg, const char a_op_code, const char * a_prefix, const char * a_group,
+                                                     const char * a_key, const void * a_value,
+                                                     const size_t a_value_len)
+{
+    (void) a_op_code;
+
+    if (a_arg) {
+        dap_chain_net_t * l_net = (dap_chain_net_t *) a_arg;
+        s_net_set_go_sync(l_net);
+        /*if (!PVT (l_net)->load_mode ){
+            if( pthread_mutex_trylock( &PVT (l_net)->state_mutex) == 0 ){
+                if ( PVT(l_net)->state == NET_STATE_ONLINE || PVT(l_net)->state == NET_STATE_ONLINE  )
+                    dap_chain_net_sync_all(l_net);
+                pthread_mutex_unlock( &PVT (l_net)->state_mutex);
+            }
+        }*/
+    }
+}
+
+/**
+ * @brief s_chain_callback_notify
+ * @param a_arg
+ * @param a_chain
+ * @param a_id
+ */
+static void s_chain_callback_notify(void * a_arg, dap_chain_t *a_chain, dap_chain_cell_id_t a_id)
+{
+    if(!a_arg)
+        return;
+    dap_chain_net_t * l_net = (dap_chain_net_t *) a_arg;
+    s_net_set_go_sync(l_net);
+}
+
+
+/**
+ * @brief s_net_states_proc
+ * @param l_net
+ */
+static int s_net_states_proc(dap_chain_net_t * l_net)
+{
+#if DAP_DEBUG
+    dap_chain_net_pvt_t *pvt_debug = PVT(l_net);
+#endif
+    int ret=0;
+
+    switch ( PVT(l_net)->state ){
+        case NET_STATE_OFFLINE:{
+            PVT(l_net)->state_target = PVT(l_net)->state_new;
+            // reset current link
+            PVT(l_net)->links_count = 0;
+            // delete all links
+            dap_chain_node_client_close(PVT(l_net)->links);
+            PVT(l_net)->links_addrs_count = 0;
+            if ( PVT(l_net)->links_addrs )
+                DAP_DELETE(PVT(l_net)->links_addrs);
+            PVT(l_net)->links_addrs = NULL;
+
+            if ( PVT(l_net)->state_target != NET_STATE_OFFLINE ){
+                PVT(l_net)->state = NET_STATE_LINKS_PREPARE;
+                break;
+            }
+            // disable SYNC_GDB
+            PVT(l_net)->flags &= ~F_DAP_CHAIN_NET_GO_SYNC;
+            PVT(l_net)->last_sync = 0;
+        } break;
+        case NET_STATE_LINKS_PREPARE:{
+            log_it(L_NOTICE,"%s.state: NET_STATE_LINKS_PREPARE",l_net->pub.name);
+            switch (PVT(l_net)->node_role.enums) {
+                case NODE_ROLE_ROOT:
+                case NODE_ROLE_ROOT_MASTER:
+                case NODE_ROLE_ARCHIVE:
+                case NODE_ROLE_CELL_MASTER:{
+                    // This roles load predefined links from global_db
+                    if ( PVT(l_net)->node_info ) {
+                        if (PVT(l_net)->links_addrs )
+                            DAP_DELETE(PVT(l_net)->links_addrs);
+                        PVT(l_net)->links_addrs_count = PVT(l_net)->node_info->hdr.links_number;
+                        PVT(l_net)->links_addrs = DAP_NEW_Z_SIZE( dap_chain_node_addr_t,
+                                                                  PVT(l_net)->links_addrs_count * sizeof(dap_chain_node_addr_t));
+                        for (size_t i =0 ; i < PVT(l_net)->node_info->hdr.links_number; i++ ){
+                            PVT(l_net)->links_addrs[i].uint64 = PVT(l_net)->node_info->links[i].uint64;
+                        }
+                    }else {
+                        log_it(L_WARNING,"No nodeinfo in global_db to prepare links for connecting, find nearest 3 links and fill global_db");
+                    }
+
+                    // add other root nodes for connect
+                    //if(!PVT(l_net)->links_addrs_count)
+                    {
+                        // use no more then 4 root node
+                        int l_use_root_nodes = min(4, PVT(l_net)->seed_aliases_count);
+                        if(!PVT(l_net)->links_addrs_count) {
+                            PVT(l_net)->links_addrs = DAP_NEW_Z_SIZE(dap_chain_node_addr_t,
+                                    l_use_root_nodes * sizeof(dap_chain_node_addr_t));
+                        }
+                        else{
+                            PVT(l_net)->links_addrs = DAP_REALLOC(PVT(l_net)->links_addrs,
+                                    (PVT(l_net)->links_addrs_count+l_use_root_nodes) * sizeof(dap_chain_node_addr_t));
+                            memset(PVT(l_net)->links_addrs + PVT(l_net)->links_addrs_count, 0,
+                                    l_use_root_nodes * sizeof(dap_chain_node_addr_t));
+                        }
+
+                        for(uint16_t i = 0; i < l_use_root_nodes; i++) {
+                            dap_chain_node_addr_t * l_node_addr = dap_chain_node_alias_find(l_net, PVT(l_net)->seed_aliases[i]);
+                            if(l_node_addr) {
+                                PVT(l_net)->links_addrs[PVT(l_net)->links_addrs_count].uint64 = l_node_addr->uint64;
+                                PVT(l_net)->links_addrs_count++;
+                            }
+                        }
+                    }
+                    // shuffle the order of the nodes
+                    for(size_t i = 0; i < PVT(l_net)->links_addrs_count; i++) {
+                        unsigned int l_new_node_pos = rand() % (PVT(l_net)->links_addrs_count);
+                        if(i == l_new_node_pos)
+                            continue;
+                        uint64_t l_tmp_uint64 = PVT(l_net)->links_addrs[i].uint64;
+                        PVT(l_net)->links_addrs[i].uint64 = PVT(l_net)->links_addrs[l_new_node_pos].uint64;
+                        PVT(l_net)->links_addrs[l_new_node_pos].uint64 = l_tmp_uint64;
+                    }
+
+
+                } break;
+                case NODE_ROLE_FULL:
+                case NODE_ROLE_MASTER:
+                case NODE_ROLE_LIGHT:{
+                    // If we haven't any assigned shard - connect to root-0
+                    if(1) { //if(l_net->pub.cell_id.uint64 == 0) {
+
+                    //dap_chain_net_pvt_t *pvt_debug = PVT(l_net);
+                    // get current node address
+                    dap_chain_node_addr_t l_address;
+                    l_address.uint64 = dap_chain_net_get_cur_addr(l_net) ?
+                                         dap_chain_net_get_cur_addr(l_net)->uint64 :
+                                         dap_db_get_cur_node_addr(l_net->pub.name);
+
+                    // get current node info
+                    dap_chain_node_info_t *l_cur_node_info = dap_chain_node_info_read(l_net, &l_address);
+
+                    if ( l_cur_node_info ) {
+                        uint16_t l_links_addrs_count = l_cur_node_info->hdr.links_number + PVT(l_net)->seed_aliases_count;
+                        PVT(l_net)->links_addrs = DAP_NEW_Z_SIZE(dap_chain_node_addr_t,
+                                l_links_addrs_count * sizeof(dap_chain_node_addr_t));
+
+                        // add linked nodes for connect
+                        for(uint16_t i = 0; i < min(4, l_cur_node_info->hdr.links_number); i++) {
+                            dap_chain_node_addr_t *l_addr = l_cur_node_info->links + i;
+                            //dap_chain_node_addr_t link_addr = l_cur_node_info->links[i];
+                            dap_chain_node_info_t *l_remore_node_info = dap_chain_node_info_read(l_net, l_addr);
+                            if(l_remore_node_info) {
+                                // if only nodes from the same cell of cell=0
+                                if(!l_cur_node_info->hdr.cell_id.uint64 ||
+                                    l_cur_node_info->hdr.cell_id.uint64 == l_remore_node_info->hdr.cell_id.uint64) {
+                                    PVT(l_net)->links_addrs[PVT(l_net)->links_addrs_count].uint64 =
+                                            l_remore_node_info->hdr.address.uint64;
+                                    PVT(l_net)->links_addrs_count++;
+                                }
+                                DAP_DELETE(l_remore_node_info);
+                            }
+                        }
+                    }
+                    // if no links then add root nodes for connect
+                    if(!PVT(l_net)->links_addrs_count){
+                        // use no more then 4 root node
+                        int l_use_root_nodes = min(4, PVT(l_net)->seed_aliases_count);
+                        PVT(l_net)->links_addrs = DAP_NEW_Z_SIZE(dap_chain_node_addr_t,
+                                l_use_root_nodes * sizeof(dap_chain_node_addr_t));
+
+                        for(uint16_t i = 0; i < l_use_root_nodes; i++) {
+                            dap_chain_node_addr_t * l_node_addr = dap_chain_node_alias_find(l_net, PVT(l_net)->seed_aliases[i]);
+                            if(l_node_addr) {
+                                PVT(l_net)->links_addrs[PVT(l_net)->links_addrs_count].uint64 = l_node_addr->uint64;
+                                PVT(l_net)->links_addrs_count++;
+                            }
+                        }
+                    }
+                    // shuffle the order of the nodes
+                    for(size_t i = 0; i < PVT(l_net)->links_addrs_count; i++) {
+                        unsigned int l_new_node_pos = rand() % (PVT(l_net)->links_addrs_count);
+                        if(i==l_new_node_pos)
+                            continue;
+                        uint64_t l_tmp_uint64 = PVT(l_net)->links_addrs[i].uint64;
+                        PVT(l_net)->links_addrs[i].uint64 = PVT(l_net)->links_addrs[l_new_node_pos].uint64;
+                        PVT(l_net)->links_addrs[l_new_node_pos].uint64 = l_tmp_uint64;
+                    }
+                    DAP_DELETE(l_cur_node_info);
+                }else {
+                        // TODO read cell's nodelist and populate array with it
+                    }
+                } break;
+            }
+            if ( PVT(l_net)->state_target > NET_STATE_LINKS_PREPARE ){
+                if ( PVT(l_net)->links_addrs_count>0 ) { // If links are present
+                    PVT(l_net)->state = NET_STATE_LINKS_CONNECTING;
+                    log_it(L_DEBUG,"Prepared %u links, start to establish them", PVT(l_net)->links_addrs_count );
+                } else {
+                    log_it(L_WARNING,"No links for connecting, return back to OFFLINE state");
+                    PVT(l_net)->state = NET_STATE_OFFLINE;
+                    // remove looping
+                    PVT(l_net)->state_target = NET_STATE_OFFLINE;
+                }
+            }else {
+                log_it(L_WARNING,"Target state is NET_STATE_LINKS_PREPARE? Realy?");
+                PVT(l_net)->state = NET_STATE_OFFLINE;
+            }
+        }
+        break;
+
+        case NET_STATE_LINKS_CONNECTING:{
+            size_t l_links_count = PVT(l_net)->links_count;
+            if(l_links_count >= s_required_links_count || (l_links_count + 1) >= s_max_links_count) {
+                // TODO what if other failed and we want more?
+            }
+            if (l_links_count < PVT(l_net)->links_addrs_count) {
+                PVT(l_net)->links_count++;
+            } else {
+                log_it(L_NOTICE, "Can't establish enough links, go to offline");
+                PVT(l_net)->state = NET_STATE_OFFLINE;
+                PVT(l_net)->state_target = NET_STATE_OFFLINE;
+                break;
+            }
+            log_it(L_DEBUG, "%s.state: NET_STATE_LINKS_CONNECTING",l_net->pub.name);
+            log_it(L_DEBUG, "Establishing connection with " NODE_ADDR_FP_STR,
+                   NODE_ADDR_FP_ARGS_S( PVT(l_net)->links_addrs[l_links_count]) );
+            dap_chain_node_info_t *l_link_node_info = dap_chain_node_info_read(l_net, &PVT(l_net)->links_addrs[l_links_count]);
+            if ( l_link_node_info ) {
+                dap_chain_node_client_t *l_node_client = dap_chain_node_client_connect(l_link_node_info);
+                if(!l_node_client) {
+                    DAP_DELETE(l_link_node_info);
+                    ret = -1;
+                    break;
+                }
+                // wait connected
+                int timeout_ms = 5000; //5 sec = 5000 ms
+                int res = dap_chain_node_client_wait(l_node_client, NODE_CLIENT_STATE_CONNECTED, timeout_ms);
+                if (res == 0 ){
+                    log_it(L_DEBUG, "Connected link %u", l_links_count);
+                    PVT(l_net)->links = l_node_client;
+                    PVT(l_net)->state = NET_STATE_LINKS_ESTABLISHED;
+                }else {
+                    log_it(L_DEBUG, "Cant establish link %u", l_links_count);
+                    dap_chain_node_client_close(l_node_client);
+                }
+            }
+        }
+        break;
+
+        case NET_STATE_LINKS_ESTABLISHED:{
+            log_it(L_DEBUG,"%s.state: NET_STATE_LINKS_ESTABLISHED",l_net->pub.name);
+            switch (PVT(l_net)->state_target) {
+                case NET_STATE_ONLINE:{ // Online
+                    switch ( PVT(l_net)->node_role.enums ){
+                        case NODE_ROLE_ROOT_MASTER:
+                        case NODE_ROLE_ROOT:{
+                            /*dap_chain_node_client_t * l_node_client = NULL, *l_node_client_tmp = NULL;
+
+                            // Send everybody your address when linked
+                            HASH_ITER(hh,PVT(l_net)->links,l_node_client,l_node_client_tmp){
+                                dap_stream_ch_chain_net_pkt_write(dap_client_get_stream_ch(
+                                                  l_node_client->client, dap_stream_ch_chain_net_get_id()),
+                                                   DAP_STREAM_CH_CHAIN_NET_PKT_TYPE_NODE_ADDR, l_net->pub.id,
+                                                   dap_chain_net_get_cur_addr(l_net),
+                                                   sizeof (dap_chain_node_addr_t) );
+                            }*/
+                            PVT(l_net)->state = NET_STATE_SYNC_GDB;
+                        }break;
+                        case NODE_ROLE_CELL_MASTER:
+                        case NODE_ROLE_MASTER:{
+                            PVT(l_net)->state = NET_STATE_SYNC_GDB;//NET_STATE_ADDR_REQUEST;
+                        } break;
+                       default:{
+                        // get addr for current node if it absent
+                        if(!dap_chain_net_get_cur_addr_int(l_net))
+                            PVT(l_net)->state = NET_STATE_ADDR_REQUEST;
+                        else
+                            PVT( l_net)->state = NET_STATE_SYNC_GDB;
+                       }
+                    }
+                }
+                break;
+
+                case NET_STATE_SYNC_GDB: // we need only to sync gdb
+                    PVT(l_net)->state = NET_STATE_SYNC_GDB ;
+                    if ( PVT(l_net)->addr_request_attempts >=10 && PVT(l_net)->state == NET_STATE_ADDR_REQUEST){
+                        PVT(l_net)->addr_request_attempts = 0;
+                        switch( PVT(l_net)->state_target){
+                            case NET_STATE_ONLINE:
+                            case NET_STATE_SYNC_GDB:
+                                PVT(l_net)->state = NET_STATE_SYNC_GDB;
+                                break;
+
+                            case NET_STATE_SYNC_CHAINS:
+                                PVT(l_net)->state = NET_STATE_SYNC_CHAINS;
+                                break;
+                            default: {
+                                PVT(l_net)->state = NET_STATE_OFFLINE;
+                                PVT(l_net)->state_target = NET_STATE_OFFLINE;
+                            }
+                        }
+                    }
+                    break;
+
+                case NET_STATE_SYNC_CHAINS:
+                    PVT(l_net)->state = (PVT(l_net)->node_info && PVT(l_net)->node_info->hdr.address.uint64)?
+                                NET_STATE_SYNC_CHAINS : NET_STATE_ADDR_REQUEST;
+                    break;
+
+                case NET_STATE_ADDR_REQUEST :
+                    PVT(l_net)->state = NET_STATE_ADDR_REQUEST;
+                    break;
+                default: break;
+            }
+        } break;
+        // get addr for remote node
+        case NET_STATE_ADDR_REQUEST: {
+            int l_is_addr_leased = 0;
+            dap_chain_node_client_t *l_node_client = PVT(l_net)->links;
+            uint8_t l_ch_id = dap_stream_ch_chain_net_get_id(); // Channel id for chain net request
+            dap_stream_ch_t *l_ch_chain = dap_client_get_stream_ch(l_node_client->client, l_ch_id);
+            // set callback for l_ch_id
+            dap_chain_node_client_set_callbacks(l_node_client->client, l_ch_id);
+            // send request for new address
+            size_t res = dap_stream_ch_chain_net_pkt_write(l_ch_chain,
+                    DAP_STREAM_CH_CHAIN_NET_PKT_TYPE_NODE_ADDR_LEASE_REQUEST,
+                    //DAP_STREAM_CH_CHAIN_NET_PKT_TYPE_NODE_ADDR_REQUEST,
+                    l_net->pub.id, NULL, 0);
+            if (res == 0) {
+                log_it(L_WARNING,"Can't send NODE_ADDR_REQUEST packet");
+                dap_chain_node_client_close(l_node_client);
+                PVT(l_net)->state = NET_STATE_LINKS_CONNECTING;
+                break; // try with another link
+            }
+            // wait for finishing of request
+            int timeout_ms = 5000; // 5 sec = 5 000 ms
+            // TODO add progress info to console
+            PVT(l_net)->addr_request_attempts++;
+            int l_res = dap_chain_node_client_wait(l_node_client, NODE_CLIENT_STATE_NODE_ADDR_LEASED, timeout_ms);
+            switch (l_res) {
+                case -1:
+                    log_it(L_WARNING,"Timeout with addr leasing");
+                    // try again 3 times
+                    if (PVT(l_net)->addr_request_attempts < 3) {
+                        break;
+                    }
+                    PVT(l_net)->state = NET_STATE_LINKS_CONNECTING;
+                    break; // try with another link
+                case 0:
+                    log_it(L_INFO, "Node address leased");
+                    l_is_addr_leased++;
+                    PVT(l_net)->addr_request_attempts = 0;
+                    break;
+                default:
+                    if (l_node_client->last_error[0]) {
+                        log_it(L_INFO, "Node address request error %d: \"%s\"", l_res, l_node_client->last_error);
+                        l_node_client->last_error[0] = '\0';
+                    }
+                    log_it(L_INFO, "Node address request error %d", l_res);
+                    PVT(l_net)->state = NET_STATE_LINKS_CONNECTING;
+                    break;
+            }
+            if (l_is_addr_leased > 0) {
+                PVT(l_net)->state = NET_STATE_SYNC_GDB;
+            }
+        }
+        break;
+        case NET_STATE_SYNC_GDB:{
+            // send request
+            dap_chain_node_client_t *l_node_client = PVT(l_net)->links;
+            dap_stream_ch_chain_sync_request_t l_sync_gdb = {};
+            // Get last timestamp in log
+            l_sync_gdb.id_start = (uint64_t) dap_db_log_get_last_id_remote(l_node_client->remote_node_addr.uint64);
+            // no limit
+            l_sync_gdb.id_end = (uint64_t)0;
+
+            l_sync_gdb.node_addr.uint64 = dap_chain_net_get_cur_addr(l_net) ?
+                                              dap_chain_net_get_cur_addr(l_net)->uint64 :
+                                              dap_db_get_cur_node_addr(l_net->pub.name);
+
+            dap_chain_id_t l_chain_id_null = {};
+            dap_chain_cell_id_t l_chain_cell_id_null = {};
+            l_chain_id_null.uint64 = l_net->pub.id.uint64;
+            l_chain_cell_id_null.uint64 = dap_chain_net_get_cur_cell(l_net) ? dap_chain_net_get_cur_cell(l_net)->uint64 : 0;
+
+            log_it(L_DEBUG, "Prepared request to gdb sync from %llu to %llu", l_sync_gdb.id_start, l_sync_gdb.id_end);
+            // find dap_chain_id_t
+            dap_chain_t *l_chain = dap_chain_net_get_chain_by_name(l_net, "gdb");
+            dap_chain_id_t l_chain_id = l_chain ? l_chain->id : (dap_chain_id_t ) {};
+
+            size_t l_res = dap_stream_ch_chain_pkt_write(dap_client_get_stream_ch(l_node_client->client, dap_stream_ch_chain_get_id()),
+                                                        DAP_STREAM_CH_CHAIN_PKT_TYPE_SYNC_GLOBAL_DB, l_net->pub.id, l_chain_id,
+                                                        l_net->pub.cell_id, &l_sync_gdb, sizeof(l_sync_gdb));
+            if (l_res == 0) {
+                log_it(L_WARNING, "Can't send GDB sync request");
+                dap_chain_node_client_close(l_node_client);
+                PVT(l_net)->state = NET_STATE_LINKS_CONNECTING;
+                break;  //try another link
+            }
+
+            // wait for finishing of request
+            int timeout_ms = 300000; // 5 min = 300 sec = 300 000 ms
+            // TODO add progress info to console
+            l_res = dap_chain_node_client_wait(l_node_client, NODE_CLIENT_STATE_SYNCED, timeout_ms);
+            switch (l_res) {
+            case -1:
+                log_it(L_WARNING,"Timeout with link sync");
+                break;
+            case 0:
+                log_it(L_INFO, "Node sync completed");
+                break;
+            default:
+                log_it(L_INFO, "Node sync error %d",l_res);
+            }
+            if (l_res) { // try another link
+                break;
+            }
+            if(PVT(l_net)->state_target >= NET_STATE_SYNC_CHAINS){
+                PVT(l_net)->state = NET_STATE_SYNC_CHAINS;
+            } else {
+                PVT(l_net)->state = NET_STATE_ONLINE;
+            }
+        }
+        break;
+
+        case NET_STATE_SYNC_CHAINS: {
+            dap_chain_node_client_t *l_node_client = PVT(l_net)->links;
+            uint8_t l_ch_id = dap_stream_ch_chain_get_id(); // Channel id for global_db and chains sync
+            dap_stream_ch_t *l_ch_chain = dap_client_get_stream_ch(l_node_client->client, l_ch_id);
+            if(!l_ch_chain) {
+                log_it(L_DEBUG,"Can't get stream_ch for id='%c' ", l_ch_id);
+                PVT(l_net)->state = NET_STATE_LINKS_CONNECTING;
+                break;
+            }
+            dap_chain_t * l_chain = NULL;
+            int l_sync_errors = 0;
+            DL_FOREACH(l_net->pub.chains, l_chain ){
+                //size_t l_lasts_size = 0;
+                //dap_chain_atom_ptr_t * l_lasts;
+                //dap_chain_atom_iter_t * l_atom_iter = l_chain->callback_atom_iter_create(l_chain);
+                //l_lasts = l_chain->callback_atom_iter_get_lasts(l_atom_iter, &l_lasts_size);
+                //if( l_lasts ) {
+                    l_node_client->state = NODE_CLIENT_STATE_CONNECTED;
+                    dap_stream_ch_chain_sync_request_t l_request = { { 0 } };
+                    //dap_hash_fast(l_lasts[0], l_chain->callback_atom_get_size(l_lasts[0]), &l_request.hash_from);
+                    dap_stream_ch_chain_pkt_write(l_ch_chain,
+                    DAP_STREAM_CH_CHAIN_PKT_TYPE_SYNC_CHAINS, l_net->pub.id, l_chain->id,
+                            l_net->pub.cell_id, &l_request, sizeof(l_request));
+                    //
+                    //                        dap_chain_node_client_send_ch_pkt(l_node_client,l_ch_id,
+                    //                                                      DAP_STREAM_CH_CHAIN_PKT_TYPE_SYNC_CHAINS,
+                    //                                                      &l_request,sizeof (l_request) );
+
+                    // wait for finishing of request
+                    int timeout_ms = 120000; // 2 min = 120 sec = 120 000 ms
+                    // TODO add progress info to console
+                    int l_res = dap_chain_node_client_wait(l_node_client, NODE_CLIENT_STATE_SYNCED, timeout_ms);
+                    switch (l_res) {
+                    case -1:
+                        log_it(L_WARNING,"Timeout with sync of chain '%s' ", l_chain->name);
+                        break;
+                    case 0:
+                        // flush global_db
+                        dap_chain_global_db_flush();
+                        log_it(L_INFO, "sync of chain '%s' completed ", l_chain->name);
+                        // set time of last sync
+                        {
+                            struct timespec l_to;
+                            clock_gettime( CLOCK_MONOTONIC, &l_to);
+                            PVT(l_net)->last_sync = l_to.tv_sec;
+                        }
+                        break;
+                    default:
+                        log_it(L_INFO, "sync of chain '%s' error %d", l_chain->name,l_res);
+                    }
+                    if (l_res) {
+                        l_sync_errors++;
+                    }
+                    //DAP_DELETE( l_lasts );
+                //}
+                //DAP_DELETE( l_atom_iter );
+            }
+            dap_chain_node_client_close(l_node_client);
+            if (l_sync_errors) {
+                PVT(l_net)->state = NET_STATE_LINKS_CONNECTING;
+                break;
+            }
+            log_it(L_INFO, "Synchronization done");
+            PVT(l_net)->state = NET_STATE_ONLINE;
+            PVT(l_net)->links_count = 0;
+        }
+        break;
+
+        case NET_STATE_ONLINE: {
+            PVT(l_net)->state_target = PVT(l_net)->state_new;
+            switch ( PVT(l_net)->state_target) {
+                // disconnect
+                case NET_STATE_OFFLINE:
+                    PVT(l_net)->state = NET_STATE_OFFLINE;
+                    log_it(L_NOTICE, "Going to disconnet");
+                    break;
+                case NET_STATE_ONLINE:
+                    if((PVT(l_net)->flags & F_DAP_CHAIN_NET_GO_SYNC) == 0)
+                        break;
+                case NET_STATE_SYNC_GDB:
+                case NET_STATE_SYNC_CHAINS:
+                    // if flag set then go to SYNC_GDB
+                    PVT(l_net)->state = NET_STATE_LINKS_CONNECTING;
+                    break;
+                default: break;
+            }
+        }
+        break;
+        default: log_it (L_DEBUG, "Unprocessed state");
+    }
+    return ret;
+}
+
+// Global
+/*static void s_net_proc_thread_callback_update_db(void)
+{
+    dap_chain_net_item_t *l_net_item, *l_net_item_tmp;
+    printf("update0\n");
+    HASH_ITER(hh, s_net_items, l_net_item, l_net_item_tmp)
+    {
+        s_net_set_go_sync(l_net_item->chain_net);
+    }
+    printf("update1\n");
+}*/
+
+/**
+ * @brief s_net_proc_thread
+ * @details Brings up and check the Dap Chain Network
+ * @param a_cfg Network1 configuration
+ * @return
+ */
+static void *s_net_proc_thread ( void *a_net )
+{
+    dap_chain_net_t *l_net = (dap_chain_net_t *)a_net;
+    dap_chain_net_pvt_t *p_net = (dap_chain_net_pvt_t *)(void *)l_net->pvt;
+
+    // set callback to update data
+    //dap_chain_global_db_set_callback_for_update_base(s_net_proc_thread_callback_update_db);
+
+    while( !(p_net->flags & F_DAP_CHAIN_NET_SHUTDOWN) ) {
+        // check or start sync
+        s_net_states_proc( l_net );
+        // checking whether new sync is needed
+        struct timespec l_to;
+        time_t l_sync_timeout = 300; // 300 sec = 5 min
+        clock_gettime(CLOCK_MONOTONIC, &l_to);
+        // start sync every l_sync_timeout sec
+        if(p_net->last_sync && l_to.tv_sec >= p_net->last_sync + l_sync_timeout) {
+            p_net->flags |= F_DAP_CHAIN_NET_GO_SYNC;
+        }
+    }
+
+    return NULL;
+}
+
+/**
+ * @brief net_proc_start
+ * @param a_cfg
+ */
+static void s_net_proc_thread_start( dap_chain_net_t * a_net )
+{
+    if ( pthread_create(& PVT(a_net)->proc_tid ,NULL, s_net_proc_thread, a_net) == 0 ){
+        log_it (L_NOTICE,"Network processing thread started");
+    }
+}
+
+/**
+ * @brief s_net_proc_kill
+ * @param a_net
+ */
+static void s_net_proc_kill( dap_chain_net_t * a_net )
+{
+    if ( !PVT(a_net)->proc_tid )
+        return;
+
+    log_it( L_NOTICE,"Sent KILL signal to the net process thread %d, waiting for shutdown...", PVT(a_net)->proc_tid );
+
+    PVT(a_net)->flags |= F_DAP_CHAIN_NET_SHUTDOWN;
+
+#ifndef _WIN32
+    pthread_cond_signal( &PVT(a_net)->state_proc_cond );
+#else
+    SetEvent( PVT(a_net)->state_proc_cond );
+#endif
+
+    pthread_join( PVT(a_net)->proc_tid , NULL );
+    log_it( L_NOTICE,"Net process thread %d shutted down", PVT(a_net)->proc_tid );
+
+    PVT(a_net)->proc_tid = 0;
+
+    return;
+}
+
+dap_chain_node_role_t dap_chain_net_get_role(dap_chain_net_t * a_net)
+{
+    return  PVT(a_net)->node_role;
+}
+
+/**
+ * @brief dap_chain_net_new
+ * @param a_id
+ * @param a_name
+ * @param a_node_role
+ * @param a_node_name
+ * @return
+ */
+static dap_chain_net_t *s_net_new(const char * a_id, const char * a_name ,
+                                    const char * a_node_role)
+{
+    dap_chain_net_t *ret = DAP_NEW_Z_SIZE( dap_chain_net_t, sizeof(ret->pub) + sizeof(dap_chain_net_pvt_t) );
+    ret->pub.name = strdup( a_name );
+
+#ifndef _WIN32
+    pthread_condattr_t l_attr;
+    pthread_condattr_init( &l_attr );
+    pthread_condattr_setclock( &l_attr, CLOCK_MONOTONIC );
+    pthread_cond_init( &PVT(ret)->state_proc_cond, &l_attr );
+#else
+    PVT(ret)->state_proc_cond = CreateEventA( NULL, FALSE, FALSE, NULL );
+#endif
+
+    //    if ( sscanf(a_id,"0x%016lx", &ret->pub.id.uint64 ) == 1 ){
+    if ( sscanf(a_id,"0x%016llx", &ret->pub.id.uint64 ) == 1 ){
+        if (strcmp (a_node_role, "root_master")==0){
+            PVT(ret)->node_role.enums = NODE_ROLE_ROOT_MASTER;
+            log_it (L_NOTICE, "Node role \"root master\" selected");
+        } else if (strcmp( a_node_role,"root") == 0){
+            PVT(ret)->node_role.enums = NODE_ROLE_ROOT;
+            log_it (L_NOTICE, "Node role \"root\" selected");
+
+        } else if (strcmp( a_node_role,"archive") == 0){
+            PVT(ret)->node_role.enums = NODE_ROLE_ARCHIVE;
+            log_it (L_NOTICE, "Node role \"archive\" selected");
+
+        } else if (strcmp( a_node_role,"cell_master") == 0){
+            PVT(ret)->node_role.enums = NODE_ROLE_CELL_MASTER;
+            log_it (L_NOTICE, "Node role \"cell master\" selected");
+
+        }else if (strcmp( a_node_role,"master") == 0){
+            PVT(ret)->node_role.enums = NODE_ROLE_MASTER;
+            log_it (L_NOTICE, "Node role \"master\" selected");
+
+        }else if (strcmp( a_node_role,"full") == 0){
+            PVT(ret)->node_role.enums = NODE_ROLE_FULL;
+            log_it (L_NOTICE, "Node role \"full\" selected");
+
+        }else if (strcmp( a_node_role,"light") == 0){
+            PVT(ret)->node_role.enums = NODE_ROLE_LIGHT;
+            log_it (L_NOTICE, "Node role \"light\" selected");
+
+        }else{
+            log_it(L_ERROR,"Unknown node role \"%s\"",a_node_role);
+            DAP_DELETE(ret);
+            return  NULL;
+        }
+    } else {
+        log_it (L_ERROR, "Wrong id format (\"%s\"). Must be like \"0x0123456789ABCDE\"" , a_id );
+        DAP_DELETE(ret);
+        return  NULL;
+    }
+    return ret;
+
+}
+
+/**
+ * @brief dap_chain_net_delete
+ * @param a_net
+ */
+void dap_chain_net_delete( dap_chain_net_t * a_net )
+{
+    if(PVT(a_net)->seed_aliases) {
+        DAP_DELETE(PVT(a_net)->seed_aliases);
+        PVT(a_net)->seed_aliases = NULL;
+    }
+    DAP_DELETE( PVT(a_net) );
+}
+
+
+/**
+ * @brief dap_chain_net_init
+ * @return
+ */
+int dap_chain_net_init()
+{
+    dap_chain_node_cli_cmd_item_create ("net", s_cli_net, NULL, "Network commands",
+        "net -net <chain net name> go < online | offline >\n"
+            "\tFind and establish links and stay online\n"
+        "net -net <chain net name> get status\n"
+            "\tLook at current status\n"
+        "net -net <chain net name> stats tx [-from <From time>] [-to <To time>] [-prev_sec <Seconds>] \n"
+            "\tTransactions statistics. Time format is <Year>-<Month>-<Day>_<Hours>:<Minutes>:<Seconds> or just <Seconds> \n"
+        "net -net <chain net name> sync < all | gdb | chains >\n"
+            "\tSyncronyze gdb, chains or everything\n\n"
+        "net -net <chain net name> link < list | add | del | info | establish >\n"
+            "\tList,add,del, dump or establish links\n\n"
+                                        );
+    s_seed_mode = dap_config_get_item_bool_default(g_config,"general","seed_mode",false);
+    dap_chain_global_db_add_history_group_prefix("global", GROUP_LOCAL_HISTORY);
+
+    dap_chain_global_db_add_history_callback_notify("global", s_gbd_history_callback_notify, NULL );
+
+    // maximum number of connections to other nodes
+    s_max_links_count = dap_config_get_item_int32_default(g_config, "general", "max_links", s_max_links_count);
+    // required number of connections to other nodes
+    s_required_links_count = dap_config_get_item_int32_default(g_config, "general", "require_links", s_required_links_count);
+
+    dap_chain_net_load_all();
+    return 0;
+}
+
+void dap_chain_net_load_all()
+{
+    char * l_net_dir_str = dap_strdup_printf("%s/network", dap_config_path());
+    DIR * l_net_dir = opendir( l_net_dir_str);
+    if ( l_net_dir ){
+        struct dirent * l_dir_entry;
+        while ( (l_dir_entry = readdir(l_net_dir) )!= NULL ){
+            if (l_dir_entry->d_name[0]=='\0' || l_dir_entry->d_name[0]=='.')
+                continue;
+            // don't search in directories
+            char * l_full_path = dap_strdup_printf("%s/%s", l_net_dir_str, l_dir_entry->d_name);
+            if(dap_dir_test(l_full_path)) {
+                DAP_DELETE(l_full_path);
+                continue;
+            }
+            DAP_DELETE(l_full_path);
+            // search only ".cfg" files
+            if(strlen(l_dir_entry->d_name) > 4) { // It has non zero name excluding file extension
+                if(strncmp(l_dir_entry->d_name + strlen(l_dir_entry->d_name) - 4, ".cfg", 4) != 0) {
+                    // its not .cfg file
+                    continue;
+                }
+            }
+            log_it(L_DEBUG,"Network config %s try to load", l_dir_entry->d_name);
+            //char* l_dot_pos = rindex(l_dir_entry->d_name,'.');
+            char* l_dot_pos = strchr(l_dir_entry->d_name,'.');
+            if ( l_dot_pos )
+                *l_dot_pos = '\0';
+            s_net_load(l_dir_entry->d_name );
+        }
+        closedir(l_net_dir);
+    }
+    DAP_DELETE (l_net_dir_str);
+}
+
+/**
+ * @brief s_cli_net
+ * @param argc
+ * @param argv
+ * @param arg_func
+ * @param str_reply
+ * @return
+ */
+static int s_cli_net( int argc, char **argv, void *arg_func, char **a_str_reply)
+{
+    int arg_index = 1;
+    dap_chain_net_t * l_net = NULL;
+
+    // command 'net list'
+    if(dap_chain_node_cli_find_option_val(argv, arg_index, argc, "list", NULL) == arg_index) {
+
+        dap_string_t *l_string_ret = dap_string_new("list of nets: ");
+        // show list of nets
+        dap_chain_net_item_t * l_net_item, *l_net_item_tmp;
+        int l_net_i = 0;
+        HASH_ITER(hh, s_net_items, l_net_item, l_net_item_tmp)
+        {
+            if(l_net_i > 0)
+                dap_string_append(l_string_ret, ", ");
+            dap_string_append_printf(l_string_ret, "%s", l_net_item->name);
+            l_net_i++;
+        }
+        if(!l_net_i)
+            dap_string_append(l_string_ret, "-\n");
+        else
+            dap_string_append(l_string_ret, "\n");
+
+        dap_chain_node_cli_set_reply_text(a_str_reply, l_string_ret->str);
+        dap_string_free(l_string_ret, true);
+        return 0;
+    }
+
+    int ret = dap_chain_node_cli_cmd_values_parse_net_chain( &arg_index, argc, argv, a_str_reply, NULL, &l_net );
+
+    if ( l_net ) {
+        const char *l_sync_str = NULL;
+        const char *l_links_str = NULL;
+        const char *l_go_str = NULL;
+        const char *l_get_str = NULL;
+        const char *l_stats_str = NULL;
+        dap_chain_node_cli_find_option_val(argv, arg_index, argc, "sync", &l_sync_str);
+        dap_chain_node_cli_find_option_val(argv, arg_index, argc, "link", &l_links_str);
+        dap_chain_node_cli_find_option_val(argv, arg_index, argc, "go", &l_go_str);
+        dap_chain_node_cli_find_option_val(argv, arg_index, argc, "get", &l_get_str);
+        dap_chain_node_cli_find_option_val(argv, arg_index, argc, "stats", &l_stats_str);
+
+        if ( l_stats_str ){
+            if ( strcmp(l_stats_str,"tx") == 0 ) {
+                const char *l_to_str = NULL;
+                struct tm l_to_tm = {0};
+
+                const char *l_from_str = NULL;
+                struct tm l_from_tm = {0};
+
+                const char *l_prev_sec_str = NULL;
+                time_t l_prev_sec_ts;
+
+                const char c_time_fmt[]="%Y-%m-%d_%H:%M:%S";
+
+                // Read from/to time
+                dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-from", &l_from_str);
+                dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-to", &l_to_str);
+                dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-prev_sec", &l_prev_sec_str);
+
+                if (l_from_str ) {
+                    strptime( (char *)l_from_str, c_time_fmt, &l_from_tm );
+                }
+
+                if (l_to_str) {
+                    strptime( (char *)l_to_str, c_time_fmt, &l_to_tm );
+                }
+
+                if ( l_to_str == NULL ){ // If not set '-to' - we set up current time
+                    time_t l_ts_now = time(NULL);
+                    localtime_r(&l_ts_now, &l_to_tm);
+                }
+
+                if ( l_prev_sec_str ){
+                    time_t l_ts_now = time(NULL);
+                    l_ts_now -= strtol( l_prev_sec_str, NULL,10 );
+                    localtime_r(&l_ts_now, &l_from_tm );
+                }/*else if ( l_from_str == NULL ){ // If not set '-from' we set up current time minus 10 seconds
+                    time_t l_ts_now = time(NULL);
+                    l_ts_now -= 10;
+                    localtime_r(&l_ts_now, &l_from_tm );
+                }*/
+
+                // Form timestamps from/to
+                time_t l_from_ts = mktime(&l_from_tm);
+                time_t l_to_ts = mktime(&l_to_tm);
+
+                // Produce strings
+                char l_from_str_new[50];
+                char l_to_str_new[50];
+                strftime(l_from_str_new, sizeof(l_from_str_new), c_time_fmt,&l_from_tm );
+                strftime(l_to_str_new, sizeof(l_to_str_new), c_time_fmt,&l_to_tm );
+
+
+                dap_string_t * l_ret_str = dap_string_new("Transactions statistics:\n");
+
+                dap_string_append_printf( l_ret_str, "\tFrom: %s\tTo: %s\n", l_from_str_new, l_to_str_new);
+                log_it(L_INFO, "Calc TPS from %s to %s", l_from_str_new, l_to_str_new);
+                uint64_t l_tx_count = dap_chain_ledger_count_from_to ( l_net->pub.ledger, l_from_ts, l_to_ts);
+                long double l_tps = l_to_ts == l_from_ts ? 0 :
+                                                     (long double) l_tx_count / (long double) ( l_to_ts - l_from_ts );
+                dap_string_append_printf( l_ret_str, "\tSpeed:  %.3Lf TPS\n", l_tps );
+                dap_string_append_printf( l_ret_str, "\tTotal:  %llu\n", l_tx_count );
+                dap_chain_node_cli_set_reply_text( a_str_reply, l_ret_str->str );
+                dap_string_free( l_ret_str, false );
+            }
+        } else if ( l_go_str){
+            if ( strcmp(l_go_str,"online") == 0 ) {
+                dap_chain_net_state_go_to(l_net, NET_STATE_ONLINE);
+                dap_chain_node_cli_set_reply_text(a_str_reply, "Network \"%s\" go from state %s to %s",
+                                                    l_net->pub.name,c_net_states[PVT(l_net)->state],
+                                                    c_net_states[PVT(l_net)->state_new]);
+            } else if ( strcmp(l_go_str,"offline") == 0 ) {
+                dap_chain_net_state_go_to(l_net, NET_STATE_OFFLINE);
+                dap_chain_node_cli_set_reply_text(a_str_reply, "Network \"%s\" go from state %s to %s",
+                                                    l_net->pub.name,c_net_states[PVT(l_net)->state],
+                                                    c_net_states[PVT(l_net)->state_new]);
+
+            }
+            else if(strcmp(l_go_str, "sync") == 0) {
+                dap_chain_net_state_go_to(l_net, NET_STATE_SYNC_GDB);
+                dap_chain_node_cli_set_reply_text(a_str_reply, "Network \"%s\" go from state %s to %s",
+                        l_net->pub.name, c_net_states[PVT(l_net)->state],
+                        c_net_states[PVT(l_net)->state_target]);
+
+            }
+
+        } else if ( l_get_str){
+            if ( strcmp(l_get_str,"status") == 0 ) {
+                // get current node address
+                dap_chain_node_addr_t l_cur_node_addr = { 0 };
+                l_cur_node_addr.uint64 = dap_chain_net_get_cur_addr(l_net) ? dap_chain_net_get_cur_addr(l_net)->uint64 : dap_db_get_cur_node_addr(l_net->pub.name);
+                if(!l_cur_node_addr.uint64) {
+                    dap_chain_node_cli_set_reply_text(a_str_reply,
+                            "Network \"%s\" has state %s (target state %s), active links %u from %u, cur node address not defined",
+                            l_net->pub.name, c_net_states[PVT(l_net)->state],
+                            c_net_states[PVT(l_net)->state_target], PVT(l_net)->links_count,
+                            PVT(l_net)->links_addrs_count
+                            );
+                }
+                else {
+                    dap_chain_node_cli_set_reply_text(a_str_reply,
+                            "Network \"%s\" has state %s (target state %s), active links %u from %u, cur node address " NODE_ADDR_FP_STR,
+                            l_net->pub.name, c_net_states[PVT(l_net)->state],
+                            c_net_states[PVT(l_net)->state_target], PVT(l_net)->links_count,
+                            PVT(l_net)->links_addrs_count,
+                            NODE_ADDR_FP_ARGS_S(l_cur_node_addr)
+                            );
+                }
+                ret = 0;
+            }
+        } else if ( l_links_str ){
+            if ( strcmp(l_links_str,"list") == 0 ) {
+
+            } else if ( strcmp(l_links_str,"add") == 0 ) {
+
+            } else if ( strcmp(l_links_str,"del") == 0 ) {
+
+            }  else if ( strcmp(l_links_str,"info") == 0 ) {
+
+            } else if ( strcmp (l_links_str,"disconnect_all") == 0 ){
+                ret = 0;
+                dap_chain_net_stop(l_net);
+            }else {
+                dap_chain_node_cli_set_reply_text(a_str_reply,
+                                                  "Subcommand \"link\" requires one of parameter: list\n");
+                ret = -3;
+            }
+
+        } else if( l_sync_str) {
+            if ( strcmp(l_sync_str,"all") == 0 ) {
+                dap_chain_node_cli_set_reply_text(a_str_reply,
+                                                  "SYNC_ALL state requested to state machine. Current state: %s\n",
+                                                  c_net_states[ PVT(l_net)->state] );
+                dap_chain_net_sync_all(l_net);
+            } else if ( strcmp(l_sync_str,"gdb") == 0) {
+                dap_chain_node_cli_set_reply_text(a_str_reply,
+                                                  "SYNC_GDB state requested to state machine. Current state: %s\n",
+                                                  c_net_states[ PVT(l_net)->state] );
+                dap_chain_net_sync_gdb(l_net);
+
+            }  else if ( strcmp(l_sync_str,"chains") == 0) {
+                dap_chain_node_cli_set_reply_text(a_str_reply,
+                                                  "SYNC_CHAINS state requested to state machine. Current state: %s\n",
+                                                  c_net_states[ PVT(l_net)->state] );
+                dap_chain_net_sync_chains(l_net);
+
+            } else {
+                dap_chain_node_cli_set_reply_text(a_str_reply,
+                                                  "Subcommand \"sync\" requires one of parameter: all,gdb,chains\n");
+                ret = -2;
+            }
+        } else {
+            dap_chain_node_cli_set_reply_text(a_str_reply,"Command requires one of subcomand: sync, links\n");
+            ret = -1;
+        }
+
+    }
+    return  ret;
+}
+
+// for sequential loading chains
+typedef struct list_priority_{
+    uint16_t prior;
+    char * chains_path;
+}list_priority;
+
+static int callback_compare_prioritity_list(const void * a_item1, const void * a_item2)
+{
+    list_priority *l_item1 = (list_priority*) a_item1;
+    list_priority *l_item2 = (list_priority*) a_item2;
+    if(!l_item1 || !l_item2 || l_item1->prior == l_item2->prior)
+        return 0;
+    if(l_item1->prior > l_item2->prior)
+        return 1;
+    return -1;
+}
+
+/**
+ * @brief s_net_load
+ * @param a_net_name
+ * @return
+ */
+int s_net_load(const char * a_net_name)
+{
+    dap_config_t *l_cfg=NULL;
+    dap_string_t *l_cfg_path = dap_string_new("network/");
+    dap_string_append(l_cfg_path,a_net_name);
+
+    if( ( l_cfg = dap_config_open ( l_cfg_path->str ) ) == NULL ) {
+        log_it(L_ERROR,"Can't open default network config");
+        dap_string_free(l_cfg_path,true);
+        return -1;
+    } else {
+        dap_string_free(l_cfg_path,true);
+        dap_chain_net_t * l_net = s_net_new(
+                                            dap_config_get_item_str(l_cfg , "general" , "id" ),
+                                            dap_config_get_item_str(l_cfg , "general" , "name" ),
+                                            dap_config_get_item_str(l_cfg , "general" , "node-role" )
+                                           );
+        if(!l_net) {
+            log_it(L_ERROR,"Can't create l_net");
+            return -1;
+        }
+        PVT(l_net)->load_mode = true;
+        l_net->pub.gdb_groups_prefix = dap_strdup (
+                    dap_config_get_item_str_default(l_cfg , "general" , "gdb_groups_prefix",
+                                                    dap_config_get_item_str(l_cfg , "general" , "name" ) ) );
+        dap_chain_global_db_add_history_group_prefix( l_net->pub.gdb_groups_prefix, GROUP_LOCAL_HISTORY);
+        dap_chain_global_db_add_history_callback_notify(l_net->pub.gdb_groups_prefix, s_gbd_history_callback_notify, l_net );
+
+        l_net->pub.gdb_nodes = dap_strdup_printf("%s.nodes",l_net->pub.gdb_groups_prefix);
+        l_net->pub.gdb_nodes_aliases = dap_strdup_printf("%s.nodes.aliases",l_net->pub.gdb_groups_prefix);
+
+        // for sync special groups - nodes
+        char **l_gdb_sync_nodes_addrs = dap_config_get_array_str(l_cfg, "general", "gdb_sync_nodes_addrs",
+                &PVT(l_net)->gdb_sync_nodes_addrs_count);
+        if(l_gdb_sync_nodes_addrs && PVT(l_net)->gdb_sync_nodes_addrs_count > 0) {
+            PVT(l_net)->gdb_sync_nodes_addrs = (dap_chain_node_addr_t*) DAP_NEW_Z_SIZE(char**,
+                    sizeof(dap_chain_node_addr_t)*PVT(l_net)->gdb_sync_nodes_addrs_count);
+            for(uint16_t i = 0; i < PVT(l_net)->gdb_sync_nodes_addrs_count; i++) {
+                dap_chain_node_addr_from_str(PVT(l_net)->gdb_sync_nodes_addrs + i, l_gdb_sync_nodes_addrs[i]);
+            }
+        }
+        // for sync special groups - groups
+        char **l_gdb_sync_groups = dap_config_get_array_str(l_cfg, "general", "gdb_sync_groups", &PVT(l_net)->gdb_sync_groups_count);
+        if(l_gdb_sync_groups && PVT(l_net)->gdb_sync_groups_count > 0) {
+            PVT(l_net)->gdb_sync_groups = (char **) DAP_NEW_SIZE(char**, sizeof(char*)*PVT(l_net)->gdb_sync_groups_count);
+            for(uint16_t i = 0; i < PVT(l_net)->gdb_sync_groups_count; i++) {
+                PVT(l_net)->gdb_sync_groups[i] = dap_strdup(l_gdb_sync_groups[i]);
+                // added group to history log
+                dap_list_t *l_groups0 = dap_chain_global_db_driver_get_groups_by_mask(PVT(l_net)->gdb_sync_groups[i]);
+                dap_list_t *l_groups = l_groups0;
+                while(l_groups) {
+                    char *l_group_name = l_groups->data;
+                    // do not use groups with names like *.del
+                    if(fnmatch("*.del", l_group_name, 0)) {
+                        const char *l_history_group = dap_chain_global_db_add_history_extra_group(l_group_name, PVT(l_net)->gdb_sync_nodes_addrs,
+                                PVT(l_net)->gdb_sync_nodes_addrs_count);
+                        dap_chain_global_db_add_history_extra_group_callback_notify(l_group_name,
+                                s_gbd_history_callback_notify, l_net);
+                        // create history for group
+                        if(dap_db_log_get_group_history_last_id(l_history_group) <= 0) {
+                            size_t l_data_size_out = 0;
+                            dap_store_obj_t *l_obj = dap_chain_global_db_obj_gr_get(NULL, &l_data_size_out, l_group_name);
+                            if(l_obj && l_data_size_out > 0) {
+                                dap_db_history_add('a', l_obj, l_data_size_out, l_history_group);
+                                dap_store_obj_free(l_obj, l_data_size_out);
+                            }
+                        }
+                    }
+                    l_groups = dap_list_next(l_groups);
+                }
+                dap_list_free_full(l_groups0, (dap_callback_destroyed_t)free);
+            }
+        }
+
+
+        // Add network to the list
+        dap_chain_net_item_t * l_net_item = DAP_NEW_Z( dap_chain_net_item_t);
+        dap_chain_net_item_t * l_net_item2 = DAP_NEW_Z( dap_chain_net_item_t);
+        dap_snprintf(l_net_item->name,sizeof (l_net_item->name),"%s"
+                     ,dap_config_get_item_str(l_cfg , "general" , "name" ));
+        l_net_item->chain_net = l_net;
+        l_net_item->net_id.uint64 = l_net->pub.id.uint64;
+        HASH_ADD_STR(s_net_items,name,l_net_item);
+
+        memcpy( l_net_item2,l_net_item,sizeof (*l_net_item));
+        HASH_ADD(hh,s_net_items_ids,net_id,sizeof ( l_net_item2->net_id),l_net_item2);
+
+        // LEDGER model
+        uint16_t l_ledger_flags = 0;
+        switch ( PVT( l_net )->node_role.enums ) {
+            case NODE_ROLE_ROOT_MASTER:
+            case NODE_ROLE_ROOT:
+            case NODE_ROLE_ARCHIVE:
+                l_ledger_flags |= DAP_CHAIN_LEDGER_CHECK_TOKEN_EMISSION;
+            case NODE_ROLE_MASTER:
+                l_ledger_flags |= DAP_CHAIN_LEDGER_CHECK_CELLS_DS;
+            case NODE_ROLE_CELL_MASTER:
+                l_ledger_flags |= DAP_CHAIN_LEDGER_CHECK_TOKEN_EMISSION;
+            case NODE_ROLE_FULL:
+            case NODE_ROLE_LIGHT:
+                l_ledger_flags |= DAP_CHAIN_LEDGER_CHECK_LOCAL_DS;
+        }
+        // init LEDGER model
+        l_net->pub.ledger = dap_chain_ledger_create(l_ledger_flags);
+        // Check if seed nodes are present in local db alias
+        char **l_seed_aliases = dap_config_get_array_str( l_cfg , "general" ,"seed_nodes_aliases"
+                                                             ,&PVT(l_net)->seed_aliases_count);
+        PVT(l_net)->seed_aliases = PVT(l_net)->seed_aliases_count>0 ?
+                                   (char **)DAP_NEW_SIZE(char**, sizeof(char*)*PVT(l_net)->seed_aliases_count) : NULL;
+        for(size_t i = 0; i < PVT(l_net)->seed_aliases_count; i++) {
+            PVT(l_net)->seed_aliases[i] = dap_strdup(l_seed_aliases[i]);
+        }
+
+        uint16_t l_seed_nodes_addrs_len =0;
+        char ** l_seed_nodes_addrs = dap_config_get_array_str( l_cfg , "general" ,"seed_nodes_addrs"
+                                                             ,&l_seed_nodes_addrs_len);
+
+        uint16_t l_seed_nodes_ipv4_len =0;
+        char ** l_seed_nodes_ipv4 = dap_config_get_array_str( l_cfg , "general" ,"seed_nodes_ipv4"
+                                                             ,&l_seed_nodes_ipv4_len);
+
+        uint16_t l_seed_nodes_ipv6_len =0;
+        char ** l_seed_nodes_ipv6 = dap_config_get_array_str( l_cfg , "general" ,"seed_nodes_ipv6"
+                                                             ,&l_seed_nodes_ipv6_len);
+
+        uint16_t l_seed_nodes_hostnames_len =0;
+        char ** l_seed_nodes_hostnames = dap_config_get_array_str( l_cfg , "general" ,"seed_nodes_hostnames"
+                                                             ,&l_seed_nodes_hostnames_len);
+
+        uint16_t l_seed_nodes_port_len =0;
+        char ** l_seed_nodes_port = dap_config_get_array_str( l_cfg , "general" ,"seed_nodes_port"
+                                                                     ,&l_seed_nodes_port_len);
+
+        const char * l_node_addr_type = dap_config_get_item_str_default(l_cfg , "general" ,"node_addr_type","auto");
+
+        const char * l_node_addr_str = NULL;
+        const char * l_node_alias_str = NULL;
+
+        // use unique addr from pub key
+        if(!dap_strcmp(l_node_addr_type, "auto")) {
+            size_t l_pub_key_data_size = 0;
+            uint8_t *l_pub_key_data = NULL;
+
+            // read pub key
+            l_pub_key_data = dap_chain_global_db_gr_get("cur-node-addr-pkey", &l_pub_key_data_size, GROUP_LOCAL_NODE_ADDR);
+            // generate new pub key
+            if(!l_pub_key_data || !l_pub_key_data_size){
+
+                const char * l_certs_name_str = "node-addr";
+                dap_cert_t ** l_certs = NULL;
+                size_t l_certs_size = 0;
+                dap_cert_t * l_cert = NULL;
+                // Load certs or create if not found
+                if(!dap_cert_parse_str_list(l_certs_name_str, &l_certs, &l_certs_size)) { // Load certs
+                    const char *l_cert_folder = dap_cert_get_folder(0);
+                    // create new cert
+                    if(l_cert_folder) {
+                        char *l_cert_path = dap_strdup_printf("%s/%s.dcert", l_cert_folder, l_certs_name_str);
+                        l_cert = dap_cert_generate(l_certs_name_str, l_cert_path, DAP_ENC_KEY_TYPE_SIG_DILITHIUM);
+                        DAP_DELETE(l_cert_path);
+                    }
+                }
+                if(l_certs_size > 0)
+                    l_cert = l_certs[0];
+                if(l_cert) {
+                    l_pub_key_data = dap_enc_key_serealize_pub_key(l_cert->enc_key, &l_pub_key_data_size);
+                    // save pub key
+                    if(l_pub_key_data && l_pub_key_data_size > 0)
+                        dap_chain_global_db_gr_set(dap_strdup("cur-node-addr-pkey"), (uint8_t*) l_pub_key_data, l_pub_key_data_size,
+                        GROUP_LOCAL_NODE_ADDR);
+                }
+            }
+            // generate addr from pub_key
+            dap_chain_hash_fast_t l_hash;
+            if(l_pub_key_data_size > 0 && dap_hash_fast(l_pub_key_data, l_pub_key_data_size, &l_hash) == 1) {
+                l_node_addr_str = dap_strdup_printf("%04X::%04X::%04X::%04X",
+                        (uint16_t) *(uint16_t*) (l_hash.raw),
+                        (uint16_t) *(uint16_t*) (l_hash.raw + 2),
+                        (uint16_t) *(uint16_t*) (l_hash.raw + DAP_CHAIN_HASH_FAST_SIZE - 4),
+                        (uint16_t) *(uint16_t*) (l_hash.raw + DAP_CHAIN_HASH_FAST_SIZE - 2));
+            }
+            DAP_DELETE(l_pub_key_data);
+        }
+        // use static addr from setting
+        else if(!dap_strcmp(l_node_addr_type, "static")) {
+            //const char * l_node_ipv4_str = dap_config_get_item_str(l_cfg , "general" ,"node-ipv4");
+            l_node_addr_str = dap_strdup(dap_config_get_item_str(l_cfg, "general", "node-addr"));
+            l_node_alias_str = dap_config_get_item_str(l_cfg, "general", "node-alias");
+        }
+
+        log_it (L_DEBUG, "Read %u aliases, %u address and %u ipv4 addresses, check them",
+                PVT(l_net)->seed_aliases_count,l_seed_nodes_addrs_len, l_seed_nodes_ipv4_len );
+        // save new nodes from cfg file to db
+        for ( size_t i = 0; i < PVT(l_net)->seed_aliases_count &&
+                            i < l_seed_nodes_addrs_len &&
+                            (
+                                ( l_seed_nodes_ipv4_len  && i < l_seed_nodes_ipv4_len  ) ||
+                                ( l_seed_nodes_ipv6_len  && i < l_seed_nodes_ipv6_len  ) ||
+                                ( l_seed_nodes_hostnames_len  && i < l_seed_nodes_hostnames_len  )
+                              )
+                                                                    ; i++ ){
+            dap_chain_node_addr_t * l_seed_node_addr;
+            l_seed_node_addr = dap_chain_node_alias_find(l_net, PVT(l_net)->seed_aliases[i] );
+            if (l_seed_node_addr == NULL){
+                log_it(L_NOTICE, "Not found alias %s in database, prefill it",PVT(l_net)->seed_aliases[i]);
+                dap_chain_node_info_t * l_node_info = DAP_NEW_Z(dap_chain_node_info_t);
+                l_seed_node_addr = DAP_NEW_Z(dap_chain_node_addr_t);
+                dap_snprintf( l_node_info->hdr.alias,sizeof ( l_node_info->hdr.alias),"%s",PVT(l_net)->seed_aliases[i]);
+                if (sscanf(l_seed_nodes_addrs[i],NODE_ADDR_FP_STR, NODE_ADDR_FPS_ARGS(l_seed_node_addr) ) != 4 ){
+                    log_it(L_ERROR,"Wrong address format,  should be like 0123::4567::890AB::CDEF");
+                    DAP_DELETE(l_seed_node_addr);
+                    DAP_DELETE(l_node_info);
+                    l_seed_node_addr = NULL;
+                    continue;
+                }
+                if( l_seed_node_addr ){
+                    if ( l_seed_nodes_ipv4_len )
+                        inet_pton( AF_INET, l_seed_nodes_ipv4[i],&l_node_info->hdr.ext_addr_v4);
+                    if ( l_seed_nodes_ipv6_len )
+                        inet_pton( AF_INET6, l_seed_nodes_ipv6[i],&l_node_info->hdr.ext_addr_v6);
+                    if(l_seed_nodes_port_len && l_seed_nodes_port_len >= i)
+                        l_node_info->hdr.ext_port = strtoul(l_seed_nodes_port[i], NULL, 10);
+                    else
+                        l_node_info->hdr.ext_port = 8079;
+
+                    if ( l_seed_nodes_hostnames_len ){
+                        struct addrinfo l_hints={0}, *l_servinfo, *p;
+                        int rv;
+                        l_hints.ai_family = AF_UNSPEC ;    /* Allow IPv4 or IPv6 */
+                        //l_hints.ai_flags = AI_PASSIVE;    /* For wildcard IP address */
+
+                        log_it( L_DEBUG, "Resolve %s addr", l_seed_nodes_hostnames[i]);
+                        struct hostent *l_he;
+
+                        if ( l_he = gethostbyname (l_seed_nodes_hostnames[i])   ){
+                            struct in_addr **l_addr_list = (struct in_addr **) l_he->h_addr_list;
+                            for(int i = 0; l_addr_list[i] != NULL; i++ ) {
+                                log_it( L_NOTICE, "Resolved %s to %s (ipv4)", l_seed_nodes_hostnames[i] ,
+                                        inet_ntoa( *l_addr_list[i]  ) );
+                                l_node_info->hdr.ext_addr_v4.s_addr = l_addr_list[i]->s_addr;
+                            }
+                        } else {
+                            herror("gethostname");
+                        }
+                    }
+
+                    l_node_info->hdr.address.uint64 = l_seed_node_addr->uint64;
+                    if ( l_node_info->hdr.ext_addr_v4.s_addr ||
+                            l_node_info->hdr.ext_addr_v6.s6_addr32[0] ){
+                        int l_ret;
+                        if ( (l_ret = dap_chain_node_info_save(l_net, l_node_info)) ==0 ){
+                            if (dap_chain_node_alias_register(l_net,PVT(l_net)->seed_aliases[i],l_seed_node_addr))
+                                log_it(L_NOTICE,"Seed node "NODE_ADDR_FP_STR" added to the curent list",NODE_ADDR_FP_ARGS(l_seed_node_addr) );
+                            else {
+                                log_it(L_WARNING,"Cant register alias %s for address "NODE_ADDR_FP_STR,NODE_ADDR_FP_ARGS(l_seed_node_addr));
+                            }
+                        }else{
+                            log_it(L_WARNING,"Cant save node info for address "NODE_ADDR_FP_STR" return code %d",
+                                   NODE_ADDR_FP_ARGS(l_seed_node_addr), l_ret);
+                        }
+                    }
+                    DAP_DELETE( l_seed_node_addr);
+                }else
+                    log_it(L_WARNING,"No address for seed node, can't populate global_db with it");
+                DAP_DELETE( l_node_info);
+            }else{
+                log_it(L_DEBUG,"Seed alias %s is present",PVT(l_net)->seed_aliases[i]);
+                DAP_DELETE( l_seed_node_addr);
+            }
+
+         }
+         //DAP_DELETE( l_seed_nodes_ipv4);
+         //DAP_DELETE(l_seed_nodes_addrs);
+        if ( l_node_addr_str || l_node_alias_str ){
+            dap_chain_node_addr_t * l_node_addr;
+            if ( l_node_addr_str == NULL)
+                l_node_addr = dap_chain_node_alias_find(l_net, l_node_alias_str);
+            else{
+                l_node_addr = DAP_NEW_Z(dap_chain_node_addr_t);
+                bool parse_succesfully = false;
+                if ( sscanf(l_node_addr_str, "0x%016llx",&l_node_addr->uint64 ) == 1 ){
+                    log_it(L_DEBUG, "Parse node address with format 0x016llx");
+                    parse_succesfully = true;
+                }
+                if ( !parse_succesfully && dap_chain_node_addr_from_str(l_node_addr, l_node_addr_str) == 0) {
+                    log_it(L_DEBUG, "Parse node address with format 04hX::04hX::04hX::04hX");
+                    parse_succesfully = true;
+                }
+
+                if (!parse_succesfully){
+                    log_it(L_ERROR,"Can't parse node address %s", l_node_addr_str);
+                    DAP_DELETE(l_node_addr);
+                    l_node_addr = NULL;
+                }
+                log_it(L_NOTICE, "Parse node addr " NODE_ADDR_FP_STR " successfully", NODE_ADDR_FP_ARGS(l_node_addr));
+                PVT(l_net)->node_addr = l_node_addr;
+                //}
+            }
+            if ( l_node_addr ) {
+                char *l_addr_hash_str = dap_chain_node_addr_to_hash_str(l_node_addr);
+                // save current node address
+                dap_db_set_cur_node_addr(l_node_addr->uint64, l_net->pub.name);
+                if(!l_addr_hash_str){
+                    log_it(L_ERROR,"Can't get hash string for node address!");
+                } else {
+                    PVT(l_net)->node_info = dap_chain_node_info_read (l_net, l_node_addr);
+                    if ( PVT(l_net)->node_info ) {
+                        log_it(L_NOTICE,"GDB Info: node_addr: " NODE_ADDR_FP_STR"  links: %u cell_id: 0x%016X ",
+                               NODE_ADDR_FP_ARGS(l_node_addr),
+                               PVT(l_net)->node_info->hdr.links_number,
+                               PVT(l_net)->node_info->hdr.cell_id.uint64);
+                        // save cell_id
+                        l_net->pub.cell_id.uint64 = PVT(l_net)->node_info->hdr.cell_id.uint64;
+                    }else {
+                        log_it(L_WARNING, "Not present node_info in database for our own address " NODE_ADDR_FP_STR,
+                               NODE_ADDR_FP_ARGS(l_node_addr) );
+                    }
+                }
+            }
+            else{
+                log_it(L_WARNING, "Not present our own address %s in database", (l_node_alias_str) ? l_node_alias_str: "");
+            }
+
+
+         }
+/*        // if present 'l_node_ipv4_str' and no 'l_node_addr_str' and 'l_node_alias_str'
+        if(!PVT(l_net)->node_info && l_node_ipv4_str) {
+            dap_chain_node_info_t *l_node_info = DAP_NEW_Z(dap_chain_node_info_t);
+            inet_pton( AF_INET, l_node_ipv4_str, &l_node_info->hdr.ext_addr_v4);
+            PVT(l_net)->node_info = l_node_info;
+        }*/
+
+        // Init chains
+        //size_t l_chains_path_size =strlen(dap_config_path())+1+strlen(l_net->pub.name)+1+strlen("network")+1;
+        //char * l_chains_path = DAP_NEW_Z_SIZE (char,l_chains_path_size);
+        //dap_snprintf(l_chains_path,l_chains_path_size,"%s/network/%s",dap_config_path(),l_net->pub.name);
+        char * l_chains_path = dap_strdup_printf("%s/network/%s", dap_config_path(), l_net->pub.name);
+        DIR * l_chains_dir = opendir(l_chains_path);
+        DAP_DELETE (l_chains_path);
+        if ( l_chains_dir ){
+            // for sequential loading chains
+            dap_list_t *l_prior_list = NULL;
+
+            struct dirent * l_dir_entry;
+            while ( (l_dir_entry = readdir(l_chains_dir) )!= NULL ){
+                if (l_dir_entry->d_name[0]=='\0')
+                    continue;
+                char * l_entry_name = strdup(l_dir_entry->d_name);
+                if (strlen (l_entry_name) > 4 ){ // It has non zero name excluding file extension
+                    if ( strncmp (l_entry_name+ strlen(l_entry_name)-4,".cfg",4) == 0 ) { // its .cfg file
+                        l_entry_name [strlen(l_entry_name)-4] = 0;
+                        log_it(L_DEBUG,"Open chain config \"%s\"...",l_entry_name);
+                        l_chains_path = dap_strdup_printf("network/%s/%s",l_net->pub.name,l_entry_name);
+                        dap_config_t * l_cfg = dap_config_open(l_chains_path);
+                        if(l_cfg) {
+                            list_priority *l_chain_prior = DAP_NEW_Z(list_priority);
+                            l_chain_prior->prior = dap_config_get_item_uint16_default(l_cfg, "chain", "load_priority", 100);
+                            l_chain_prior->chains_path = l_chains_path;
+                            // add chain to load list;
+                            l_prior_list = dap_list_append(l_prior_list, l_chain_prior);
+                        }
+                        // Create chain object
+//                        dap_chain_t * l_chain = dap_chain_load_from_cfg(l_net->pub.ledger, l_net->pub.name,
+//                                l_net->pub.id, l_chains_path);
+//                        if(l_chain) {
+//                            DL_APPEND(l_net->pub.chains, l_chain);
+//                            if(l_chain->callback_created)
+//                                l_chain->callback_created(l_chain, l_cfg);
+//                        }
+//                        DAP_DELETE (l_chains_path);
+                    }
+                }
+                DAP_DELETE (l_entry_name);
+            }
+            closedir(l_chains_dir);
+
+            // sort list with chains names by priority
+            l_prior_list = dap_list_sort(l_prior_list, callback_compare_prioritity_list);
+            // load chains by priority
+            dap_list_t *l_list = l_prior_list;
+            while(l_list){
+                list_priority *l_chain_prior = l_list->data;
+                // Create chain object
+                dap_chain_t * l_chain = dap_chain_load_from_cfg(l_net->pub.ledger, l_net->pub.name,
+                        l_net->pub.id, l_chain_prior->chains_path);
+                if(l_chain) {
+                    DL_APPEND(l_net->pub.chains, l_chain);
+                    if(l_chain->callback_created)
+                        l_chain->callback_created(l_chain, l_cfg);
+                    // add a callback to monitor changes in the chain
+                    dap_chain_add_callback_notify(l_chain, s_chain_callback_notify, l_net);
+                }
+                DAP_DELETE (l_chain_prior->chains_path);
+                l_list = dap_list_next(l_list);
+            }
+            dap_list_free(l_prior_list);
+
+        } else {
+            log_it(L_ERROR,"Can't any chains for network %s",l_net->pub.name);
+            PVT(l_net)->load_mode = false;
+
+            return -2;
+        }
+        // Do specific role actions post-chain created
+        switch ( PVT( l_net )->node_role.enums ) {
+            case NODE_ROLE_ROOT_MASTER:{
+                // Set to process everything in datum pool
+                dap_chain_t * l_chain = NULL;
+                DL_FOREACH(l_net->pub.chains, l_chain ) l_chain->is_datum_pool_proc = true;
+                log_it(L_INFO,"Root master node role established");
+            } // Master root includes root
+            case NODE_ROLE_ROOT:{
+                // Set to process only zerochain
+                dap_chain_id_t l_chain_id = {{0}};
+                dap_chain_t * l_chain = dap_chain_find_by_id(l_net->pub.id,l_chain_id);
+                if (l_chain )
+                   l_chain->is_datum_pool_proc = true;
+
+                PVT(l_net)->state_target = NET_STATE_ONLINE;
+                log_it(L_INFO,"Root node role established");
+            } break;
+            case NODE_ROLE_CELL_MASTER:
+            case NODE_ROLE_MASTER:{
+
+                uint16_t l_proc_chains_count=0;
+                char ** l_proc_chains = dap_config_get_array_str(l_cfg,"role-master" , "proc_chains", &l_proc_chains_count );
+                for ( size_t i = 0; i< l_proc_chains_count ; i++){
+                    dap_chain_id_t l_chain_id = {{0}};
+                    if(dap_sscanf( l_proc_chains[i], "0x%16lX",  &l_chain_id.uint64) ==1 || dap_scanf("0x%16lx",  &l_chain_id.uint64) == 1){
+                        dap_chain_t * l_chain = dap_chain_find_by_id(l_net->pub.id, l_chain_id );
+                        if ( l_chain ){
+                            l_chain->is_datum_pool_proc = true;
+                        }else{
+                            log_it( L_WARNING, "Can't find chain id " );
+                        }
+                    }
+                    DAP_DELETE( l_proc_chains[i]);
+                    l_proc_chains[i] = NULL;
+                }
+                //if ( l_proc_chains )
+                //    DAP_DELETE (l_proc_chains);
+                //l_proc_chains = NULL;
+
+                PVT(l_net)->state_target = NET_STATE_ONLINE;
+                log_it(L_INFO,"Master node role established");
+            } break;
+            case NODE_ROLE_FULL:{
+                log_it(L_INFO,"Full node role established");
+                PVT(l_net)->state_target = NET_STATE_ONLINE;
+            } break;
+            case NODE_ROLE_LIGHT:
+            default:
+                log_it(L_INFO,"Light node role established");
+
+        }
+
+        if (s_seed_mode || !dap_config_get_item_bool_default(g_config ,"general", "auto_online",false ) ) { // If we seed we do everything manual. First think - prefil list of node_addrs and its aliases
+            PVT(l_net)->state_target = NET_STATE_OFFLINE;
+        }
+        PVT(l_net)->load_mode = false;
+
+        // Start the proc thread
+        s_net_proc_thread_start(l_net);
+        log_it(L_NOTICE, "Сhain network \"%s\" initialized",l_net_item->name);
+        dap_config_close(l_cfg);
+    }
+    return 0;
+}
+
+/**
+ * @brief dap_chain_net_deinit
+ */
+void dap_chain_net_deinit()
+{
+}
+
+dap_chain_net_t **dap_chain_net_list(size_t *a_size)
+{
+    *a_size = HASH_COUNT(s_net_items);
+    dap_chain_net_t **l_net_list = DAP_NEW_SIZE(dap_chain_net_t *, (*a_size) * sizeof(dap_chain_net_t *));
+    dap_chain_net_item_t *l_current_item, *l_tmp;
+    int i = 0;
+    HASH_ITER(hh, s_net_items, l_current_item, l_tmp) {
+        l_net_list[i++] = l_current_item->chain_net;
+    }
+    return l_net_list;
+}
+
+/**
+ * @brief dap_chain_net_by_name
+ * @param a_name
+ * @return
+ */
+dap_chain_net_t * dap_chain_net_by_name( const char * a_name)
+{
+    dap_chain_net_item_t * l_net_item = NULL;
+    if(a_name)
+        HASH_FIND_STR(s_net_items,a_name,l_net_item );
+    if ( l_net_item )
+        return l_net_item->chain_net;
+    else
+        return NULL;
+}
+
+/**
+ * @brief dap_chain_ledger_by_net_name
+ * @param a_net_name
+ * @return
+ */
+dap_ledger_t * dap_chain_ledger_by_net_name( const char * a_net_name)
+{
+    dap_chain_net_t *l_net = dap_chain_net_by_name(a_net_name);
+    if(l_net)
+        return l_net->pub.ledger;
+    return NULL;
+}
+
+/**
+ * @brief dap_chain_net_by_id
+ * @param a_id
+ * @return
+ */
+dap_chain_net_t * dap_chain_net_by_id( dap_chain_net_id_t a_id)
+{
+    dap_chain_net_item_t * l_net_item = NULL;
+    HASH_FIND(hh,s_net_items_ids,&a_id,sizeof (a_id), l_net_item );
+    if ( l_net_item )
+        return l_net_item->chain_net;
+    else
+        return NULL;
+
+}
+
+
+/**
+ * @brief dap_chain_net_id_by_name
+ * @param a_name
+ * @return
+ */
+dap_chain_net_id_t dap_chain_net_id_by_name( const char * a_name)
+{
+    dap_chain_net_t *l_net = dap_chain_net_by_name( a_name );
+    dap_chain_net_id_t l_ret = {0};
+    if (l_net)
+        l_ret.uint64 = l_net->pub.id.uint64;
+    return l_ret;
+}
+
+/**
+ * @brief dap_chain_net_get_chain_by_name
+ * @param l_net
+ * @param a_name
+ * @return
+ */
+dap_chain_t * dap_chain_net_get_chain_by_name( dap_chain_net_t * l_net, const char * a_name)
+{
+   dap_chain_t * l_chain;
+   DL_FOREACH(l_net->pub.chains, l_chain){
+        if(dap_strcmp(l_chain->name,a_name) == 0)
+            return  l_chain;
+   }
+   return NULL;
+}
+
+/**
+ * @brief dap_chain_net_get_chain_by_chain_type
+ * @param a_datum_type
+ * @return
+ */
+dap_chain_t * dap_chain_net_get_chain_by_chain_type(dap_chain_net_t * l_net, dap_chain_type_t a_datum_type)
+{
+    dap_chain_t * l_chain;
+    if(!l_net)
+        return NULL;
+    DL_FOREACH(l_net->pub.chains, l_chain)
+    {
+        for(uint16_t i = 0; i < l_chain->datum_types_count; i++) {
+            if(l_chain->datum_types[i] == a_datum_type)
+                return l_chain;
+        }
+    }
+    return NULL;
+}
+
+/**
+ * @brief dap_chain_net_get_gdb_group_mempool_by_chain_type
+ * @param a_datum_type
+ * @return
+ */
+char * dap_chain_net_get_gdb_group_mempool_by_chain_type(dap_chain_net_t * l_net, dap_chain_type_t a_datum_type)
+{
+    dap_chain_t * l_chain;
+    if(!l_net)
+        return NULL;
+    DL_FOREACH(l_net->pub.chains, l_chain)
+    {
+        for(uint16_t i = 0; i < l_chain->datum_types_count; i++) {
+            if(l_chain->datum_types[i] == a_datum_type)
+                return dap_chain_net_get_gdb_group_mempool(l_chain);
+        }
+    }
+    return NULL;
+}
+
+
+/**
+ * @brief dap_chain_net_get_cur_addr
+ * @param l_net
+ * @return
+ */
+dap_chain_node_addr_t * dap_chain_net_get_cur_addr( dap_chain_net_t * l_net)
+{
+    return  PVT(l_net)->node_info? &PVT(l_net)->node_info->hdr.address: PVT(l_net)->node_addr;
+}
+
+uint64_t dap_chain_net_get_cur_addr_int(dap_chain_net_t * l_net)
+{
+    return dap_chain_net_get_cur_addr(l_net) ? dap_chain_net_get_cur_addr(l_net)->uint64 :
+                                               dap_db_get_cur_node_addr(l_net->pub.name);
+}
+
+dap_chain_cell_id_t * dap_chain_net_get_cur_cell( dap_chain_net_t * l_net)
+{
+    return  PVT(l_net)->node_info? &PVT(l_net)->node_info->hdr.cell_id: 0;
+}
+
+
+/**
+ * Get nodes list (list of dap_chain_node_addr_t struct)
+ */
+dap_list_t* dap_chain_net_get_link_node_list(dap_chain_net_t * l_net, bool a_is_only_cur_cell)
+{
+    dap_list_t *l_node_list = NULL;
+    // get cur node address
+    dap_chain_node_addr_t l_cur_node_addr = { 0 };
+    l_cur_node_addr.uint64 = dap_chain_net_get_cur_addr_int(l_net);
+
+    dap_chain_node_info_t *l_cur_node_info = dap_chain_node_info_read(l_net, &l_cur_node_addr);
+    // add links to nodes list only from the same cell
+    if(l_cur_node_info) {
+        for(unsigned int i = 0; i < l_cur_node_info->hdr.links_number; i++) {
+            bool l_is_add = true;
+            dap_chain_node_addr_t *l_remote_address = l_cur_node_info->links + i;
+            if(a_is_only_cur_cell) {
+                // get remote node list
+                dap_chain_node_info_t *l_remote_node_info = dap_chain_node_info_read(l_net, l_remote_address);
+                if(!l_remote_node_info || l_remote_node_info->hdr.cell_id.uint64 != l_cur_node_info->hdr.cell_id.uint64)
+                    l_is_add = false;
+                DAP_DELETE(l_remote_node_info);
+            }
+            if(l_is_add) {
+                dap_chain_node_addr_t *l_address = DAP_NEW(dap_chain_node_addr_t);
+                l_address->uint64 = l_cur_node_info->links[i].uint64;
+                l_node_list = dap_list_append(l_node_list, l_address);
+            }
+        }
+
+    }
+    DAP_DELETE(l_cur_node_info);
+    return l_node_list;
+}
+
+/**
+ * Get remote nodes list (list of dap_chain_node_addr_t struct)
+ */
+dap_list_t* dap_chain_net_get_node_list(dap_chain_net_t * l_net)
+{
+    dap_list_t *l_node_list = NULL;
+    /*
+     dap_chain_net_pvt_t *l_net_pvt = PVT(l_net);
+     // get nodes from seed_nodes
+     for(uint16_t i = 0; i < l_net_pvt->seed_aliases_count; i++) {
+     dap_chain_node_addr_t *l_node_address = dap_chain_node_alias_find(l_net, l_net_pvt->seed_aliases[i]);
+     l_node_list = dap_list_append(l_node_list, l_node_address);
+     }*/
+
+    // get nodes list from global_db
+    dap_global_db_obj_t *l_objs = NULL;
+    size_t l_nodes_count = 0;
+    // read all node
+    l_objs = dap_chain_global_db_gr_load(l_net->pub.gdb_nodes, &l_nodes_count);
+    if(!l_nodes_count || !l_objs)
+        return l_node_list;
+    for(size_t i = 0; i < l_nodes_count; i++) {
+        dap_chain_node_info_t *l_node_info = (dap_chain_node_info_t *) l_objs[i].value;
+        dap_chain_node_addr_t *l_address = DAP_NEW(dap_chain_node_addr_t);
+        l_address->uint64 = l_node_info->hdr.address.uint64;
+        l_node_list = dap_list_append(l_node_list, l_address);
+    }
+    dap_chain_global_db_objs_delete(l_objs, l_nodes_count);
+    return l_node_list;
+
+        // get remote node list
+        /*dap_chain_node_info_t *l_node_info = dap_chain_node_info_read(l_net, l_node_address);
+        if(!l_node_info)
+            continue;
+        // start connect
+        //debug inet_pton( AF_INET, "192.168.100.93", &l_node_info->hdr.ext_addr_v4);
+        dap_chain_node_client_t *l_node_client = dap_chain_node_client_connect(l_node_info);
+        //dap_chain_node_client_t *l_node_client = dap_chain_client_connect(l_node_info, l_stage_target, l_active_channels);
+        if(!l_node_client) {
+            DAP_DELETE(l_node_info);
+            continue;
+        }
+        // wait connected
+        int timeout_ms = 5000; //5 sec = 5000 ms
+        int res = dap_chain_node_client_wait(l_node_client, NODE_CLIENT_STATE_CONNECTED, timeout_ms);
+        if(res) {
+            // clean client struct
+            dap_chain_node_client_close(l_node_client);
+            DAP_DELETE(l_node_info);
+            continue;
+        }
+        res = dap_chain_node_client_send_nodelist_req(l_node_client);
+        if(res) {
+            // clean client struct
+            dap_chain_node_client_close(l_node_client);
+            DAP_DELETE(l_node_info);
+            continue;
+        }
+        res = dap_chain_node_client_wait(l_node_client, NODE_CLIENT_STATE_NODELIST_GOT, timeout_ms);
+        if(res) {
+            // clean client struct
+            dap_chain_node_client_close(l_node_client);
+            DAP_DELETE(l_node_info);
+            continue;
+        }
+        DAP_DELETE(l_node_info);
+        */
+}
+
+/**
+ * @brief dap_chain_net_proc_datapool
+ * @param a_net
+ */
+void dap_chain_net_proc_mempool (dap_chain_net_t * a_net)
+{
+
+    dap_string_t * l_str_tmp = dap_string_new(NULL);
+    dap_chain_t *l_chain;
+    DL_FOREACH(a_net->pub.chains, l_chain) {
+        char *l_gdb_group_mempool = dap_chain_net_get_gdb_group_mempool(l_chain);
+
+        size_t l_objs_size = 0;
+        dap_global_db_obj_t * l_objs = dap_chain_global_db_gr_load(l_gdb_group_mempool, &l_objs_size);
+        if(l_objs_size) {
+            log_it(L_INFO, "%s.%s: Found %u records :", a_net->pub.name, l_chain->name,
+                    l_objs_size);
+            size_t l_datums_size = l_objs_size;
+            dap_chain_datum_t ** l_datums = DAP_NEW_Z_SIZE(dap_chain_datum_t*,
+                    sizeof(dap_chain_datum_t*) * l_datums_size);
+            size_t l_objs_size_tmp = (l_objs_size > 15) ? min(l_objs_size, 10) : l_objs_size;
+            for(size_t i = 0; i < l_objs_size; i++) {
+                dap_chain_datum_t * l_datum = (dap_chain_datum_t*) l_objs[i].value;
+                l_datums[i] = l_datum;
+                if(i < l_objs_size_tmp) {
+                    char buf[50];
+                    time_t l_ts_create = (time_t) l_datum->header.ts_create;
+                    log_it(L_INFO, "\t\t0x%s: type_id=%s ts_create=%s data_size=%u",
+                            l_objs[i].key, c_datum_type_str[l_datum->header.type_id],
+                            ctime_r(&l_ts_create, buf), l_datum->header.data_size);
+                }
+            }
+            size_t l_objs_processed = l_chain->callback_datums_pool_proc(l_chain, l_datums, l_datums_size);
+            // Delete processed objects
+            size_t l_objs_processed_tmp = (l_objs_processed > 15) ? min(l_objs_processed, 10) : l_objs_processed;
+            for(size_t i = 0; i < l_objs_processed; i++) {
+                dap_chain_global_db_gr_del(dap_strdup(l_objs[i].key), l_gdb_group_mempool);
+                if(i < l_objs_processed_tmp) {
+                    dap_string_append_printf(l_str_tmp, "New event created, removed datum 0x%s from mempool \n",
+                            l_objs[i].key);
+                }
+            }
+            if(l_objs_processed < l_datums_size)
+                log_it(L_WARNING, "%s.%s: %d records not processed", a_net->pub.name, l_chain->name,
+                        l_datums_size - l_objs_processed);
+            dap_chain_global_db_objs_delete(l_objs, l_objs_size);
+        }
+        else {
+            log_it(L_INFO, "%s.%s: No records in mempool", a_net->pub.name, l_chain ? l_chain->name : "[no chain]");
+        }
+        DAP_DELETE(l_gdb_group_mempool);
+
+    }
+}
+
+/**
+ * @brief dap_chain_net_tx_get_by_hash
+ * @param a_net
+ * @param a_tx_hash
+ * @param a_search_type
+ * @return
+ */
+dap_chain_datum_tx_t * dap_chain_net_get_tx_by_hash(dap_chain_net_t * a_net, dap_chain_hash_fast_t * a_tx_hash,
+                                                     dap_chain_net_tx_search_type_t a_search_type)
+{
+    dap_ledger_t * l_ledger = dap_chain_ledger_by_net_name( a_net->pub.name );
+    dap_chain_datum_tx_t * l_tx = NULL;
+
+    switch (a_search_type) {
+        case TX_SEARCH_TYPE_NET:
+        case TX_SEARCH_TYPE_CELL:
+        case TX_SEARCH_TYPE_LOCAL:{
+            if ( ! l_tx ){
+                // pass all chains
+                for ( dap_chain_t * l_chain = a_net->pub.chains; l_chain; l_chain = l_chain->next){
+                    if ( l_chain->callback_tx_find_by_hash ){
+                        // try to find transaction in chain ( inside shard )
+                        l_tx = l_chain->callback_tx_find_by_hash( l_chain, a_tx_hash );
+                        if (l_tx)
+                            break;
+                    }
+                }
+            }
+        }break;
+
+        case TX_SEARCH_TYPE_NET_UNSPENT:
+        case TX_SEARCH_TYPE_CELL_UNSPENT:{
+            l_tx = dap_chain_ledger_tx_find_by_hash( l_ledger, a_tx_hash );
+        }break;
+    }
+    return l_tx;
+}
+
+/**
+ * @brief dap_chain_net_get_add_gdb_group
+ * @param a_net
+ * @param a_node_addr
+ * @return
+ */
+dap_list_t * dap_chain_net_get_add_gdb_group(dap_chain_net_t * a_net, dap_chain_node_addr_t a_node_addr)
+{
+    dap_list_t *l_list_groups = NULL;
+    if(!PVT(a_net)->gdb_sync_nodes_addrs || !a_net)
+        return NULL;
+    for(uint16_t i = 0; i < PVT(a_net)->gdb_sync_nodes_addrs_count; i++) {
+        if(a_node_addr.uint64 == PVT(a_net)->gdb_sync_nodes_addrs[i].uint64) {
+            for(uint16_t j = 0; j < PVT(a_net)->gdb_sync_groups_count; j++)
+                l_list_groups = dap_list_append(l_list_groups, PVT(a_net)->gdb_sync_groups[j]);
+        }
+    }
+    return l_list_groups;
+}
diff --git a/libdap-chain-net/dap_chain_net.h b/libdap-chain-net/dap_chain_net.h
new file mode 100644
index 0000000000000000000000000000000000000000..8b76bfcf1b80bab2dd9ba984004fa09228b40d0b
--- /dev/null
+++ b/libdap-chain-net/dap_chain_net.h
@@ -0,0 +1,153 @@
+/*
+* Authors:
+* Dmitriy Gerasimov <naeper@demlabs.net>
+* Aleksandr Lysikov <alexander.lysikov@demlabs.net>
+* Cellframe       https://cellframe.net
+* DeM Labs Inc.   https://demlabs.net
+* Copyright  (c) 2017-2019
+* All rights reserved.
+
+This file is part of CellFrame SDK the open source project
+
+CellFrame SDK is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+CellFrame SDK is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with any CellFrame SDK based project.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#ifndef _WIN32
+#include <sys/socket.h>
+#include <netinet/in.h>
+#else
+#include <winsock2.h>
+#include <windows.h>
+#include <mswsock.h>
+#include <ws2tcpip.h>
+
+#include "win32/ip.h"
+#include "win32/iphdr.h"
+#define s6_addr32 s6_addr
+#define herror perror
+#endif
+
+
+#include <stdint.h>
+#include <string.h>
+#include "dap_strfuncs.h"
+#include "dap_chain_common.h"
+#include "dap_chain_node.h"
+#include "dap_chain.h"
+#include "dap_chain_ledger.h"
+
+
+#define DAP_CHAIN_NET_NAME_MAX 32
+
+typedef  enum dap_chain_net_state{
+    NET_STATE_OFFLINE = 0,
+    NET_STATE_LINKS_PREPARE,
+    NET_STATE_LINKS_CONNECTING,
+    NET_STATE_LINKS_ESTABLISHED,
+    NET_STATE_ADDR_REQUEST, // Waiting for address assign
+    NET_STATE_SYNC_GDB,
+    NET_STATE_SYNC_CHAINS,
+    NET_STATE_ONLINE
+} dap_chain_net_state_t;
+
+typedef struct dap_chain_net{
+    struct {
+        dap_chain_net_id_t id;
+        dap_chain_cell_id_t cell_id; // Cell where the node is connected to. {{0}} if not celled(sharder) blockchain
+        char * name;
+        char * gdb_groups_prefix;
+        char * gdb_nodes_aliases;
+        char * gdb_nodes;
+
+        dap_chain_t * chains; // double-linked list of chains
+        dap_ledger_t  *ledger;
+    } pub;
+    uint8_t pvt[];
+} dap_chain_net_t;
+
+
+int dap_chain_net_init(void);
+void dap_chain_net_deinit(void);
+
+void dap_chain_net_load_all();
+
+void s_net_set_go_sync(dap_chain_net_t * a_net);
+int dap_chain_net_state_go_to(dap_chain_net_t * a_net, dap_chain_net_state_t a_new_state);
+
+inline static int dap_chain_net_start(dap_chain_net_t * a_net){ return dap_chain_net_state_go_to(a_net,NET_STATE_ONLINE); }
+inline static int dap_chain_net_stop(dap_chain_net_t * a_net) { return dap_chain_net_state_go_to(a_net,NET_STATE_OFFLINE); }
+inline static int dap_chain_net_links_establish(dap_chain_net_t * a_net) { return dap_chain_net_state_go_to(a_net,NET_STATE_LINKS_ESTABLISHED); }
+inline static int dap_chain_net_sync_chains(dap_chain_net_t * a_net) { return dap_chain_net_state_go_to(a_net,NET_STATE_SYNC_CHAINS); }
+inline static int dap_chain_net_sync_gdb(dap_chain_net_t * a_net) { return dap_chain_net_state_go_to(a_net,NET_STATE_SYNC_GDB); }
+inline static int dap_chain_net_sync_all(dap_chain_net_t * a_net) { return dap_chain_net_state_go_to(a_net,NET_STATE_SYNC_CHAINS); }//NET_STATE_ONLINE
+
+void dap_chain_net_delete( dap_chain_net_t * a_net);
+void dap_chain_net_proc_mempool (dap_chain_net_t * a_net);
+
+dap_chain_net_t * dap_chain_net_by_name( const char * a_name);
+dap_chain_net_t * dap_chain_net_by_id( dap_chain_net_id_t a_id);
+dap_chain_net_id_t dap_chain_net_id_by_name( const char * a_name);
+dap_ledger_t * dap_chain_ledger_by_net_name( const char * a_net_name);
+
+dap_chain_t * dap_chain_net_get_chain_by_name( dap_chain_net_t * l_net, const char * a_name);
+
+dap_chain_node_addr_t * dap_chain_net_get_cur_addr( dap_chain_net_t * l_net);
+uint64_t dap_chain_net_get_cur_addr_int(dap_chain_net_t * l_net);
+dap_chain_cell_id_t * dap_chain_net_get_cur_cell( dap_chain_net_t * l_net);
+
+dap_list_t* dap_chain_net_get_link_node_list(dap_chain_net_t * l_net, bool a_is_only_cur_cell);
+dap_list_t* dap_chain_net_get_node_list(dap_chain_net_t * l_net);
+
+void dap_chain_net_links_connect(dap_chain_net_t * a_net);
+
+typedef enum dap_chain_net_tx_search_type {
+    /// Search local, in memory, possible load data from drive to memory
+    TX_SEARCH_TYPE_LOCAL,
+    /// Do the request to the network if its not full node, search inside shard
+    TX_SEARCH_TYPE_CELL,
+    /// Do the request for unspent txs in cell
+    TX_SEARCH_TYPE_CELL_UNSPENT,
+    /// Do the search in whole network and request tx from others cells if need
+    TX_SEARCH_TYPE_NET,
+    /// Do the search in whole network but search only unspent
+    TX_SEARCH_TYPE_NET_UNSPENT
+}dap_chain_net_tx_search_type_t;
+
+dap_chain_datum_tx_t * dap_chain_net_get_tx_by_hash(dap_chain_net_t * a_net, dap_chain_hash_fast_t * a_tx_hash,
+                                                     dap_chain_net_tx_search_type_t a_search_type);
+
+dap_chain_node_role_t dap_chain_net_get_role(dap_chain_net_t * a_net);
+
+/**
+ * @brief dap_chain_net_get_gdb_group_mempool
+ * @param l_chain
+ * @return
+ */
+DAP_STATIC_INLINE char * dap_chain_net_get_gdb_group_mempool(dap_chain_t * l_chain)
+{
+    dap_chain_net_t * l_net = l_chain ? dap_chain_net_by_id(l_chain->net_id) : NULL;
+    if ( l_net ) {
+        const char c_mempool_group_str[]="mempool";
+		return dap_strdup_printf("%s.chain-%s.%s",l_net->pub.gdb_groups_prefix,l_chain->name,c_mempool_group_str);
+    }
+    return NULL;
+}
+
+dap_chain_t * dap_chain_net_get_chain_by_chain_type(dap_chain_net_t * l_net, dap_chain_type_t a_datum_type);
+char * dap_chain_net_get_gdb_group_mempool_by_chain_type(dap_chain_net_t * l_net, dap_chain_type_t a_datum_type);
+dap_chain_net_t **dap_chain_net_list(size_t *a_size);
+dap_list_t * dap_chain_net_get_add_gdb_group(dap_chain_net_t * a_net, dap_chain_node_addr_t a_node_addr);
+
diff --git a/libdap-chain-net/dap_chain_net_bugreport.c b/libdap-chain-net/dap_chain_net_bugreport.c
new file mode 100644
index 0000000000000000000000000000000000000000..8354eccbe2b0a97fe7332ce55f14a6f474a47750
--- /dev/null
+++ b/libdap-chain-net/dap_chain_net_bugreport.c
@@ -0,0 +1,116 @@
+/*
+ * Authors:
+ * Alexander Lysikov <alexander.lysikov@demlabs.net>
+ * DeM Labs Inc.   https://demlabs.net
+ * Kelvin Project https://github.com/kelvinblockchain
+ * Copyright  (c) 2020
+ * All rights reserved.
+
+ This file is part of DAP (Deus Applications Prototypes) the open source project
+
+ DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ DAP is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with any DAP based project.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "dap_common.h"
+#include "dap_string.h"
+#include "dap_strfuncs.h"
+#include "dap_file_utils.h"
+#include "dap_config.h"
+
+#include "http_status_code.h"
+#include "dap_http_simple.h"
+#include "dap_enc_http.h"
+#include "dap_chain_net_bugreport.h"
+
+#define LOG_TAG "chain_net_bugreport"
+
+#define BUGREPORT_URL "/bugreport"
+
+static int bugreport_write_to_file(byte_t *a_request_byte, size_t a_request_size)
+{
+    int l_ret = -2;
+    if(!a_request_byte || !a_request_size)
+        return -1;
+    char *l_dir_str = dap_strdup_printf("%s/var/bugreport", g_sys_dir_path);
+    dap_mkdir_with_parents(l_dir_str);
+
+    const time_t l_timer = time(NULL);
+    struct tm l_tm;
+    localtime_r(&l_timer, &l_tm);
+    char *l_filename_str = dap_strdup_printf("%s/%02d-%02d-%02d_%02d:%02d:%02d.brt", l_dir_str,
+            l_tm.tm_year - 100, l_tm.tm_mon, l_tm.tm_mday,
+            l_tm.tm_hour, l_tm.tm_min, l_tm.tm_sec);
+    FILE *l_fp;
+    if((l_fp = fopen(l_filename_str, "wb")) != NULL) {
+        if(fwrite(a_request_byte, 1, a_request_size, l_fp) == a_request_size)
+            l_ret = 0;
+        else
+            l_ret = -3;
+        fclose(l_fp);
+    }
+    DAP_DELETE(l_filename_str);
+    DAP_DELETE(l_dir_str);
+    return l_ret;
+}
+
+/**
+ * @brief bugreport_http_proc
+ * @param a_http_simple
+ * @param a_arg
+ */
+static void bugreport_http_proc(struct dap_http_simple *a_http_simple, void * a_arg)
+{
+    // data:text/html,<form action=http://192.168.100.92:8079/bugreport/ method=post><input name=a></form>
+    log_it(L_DEBUG, "bugreport_http_proc request");
+    http_status_code_t * return_code = (http_status_code_t*) a_arg;
+    //if(dap_strcmp(cl_st->http->url_path, BUGREPORT_URL) == 0 )
+    if(dap_strcmp(a_http_simple->http->action, "POST") == 0)
+            {
+        a_http_simple->request_byte;
+        a_http_simple->request_size;
+        a_http_simple->http->in_content_length;
+        if(!bugreport_write_to_file(a_http_simple->request_byte, a_http_simple->request_size)) {
+            a_http_simple->reply = dap_strdup_printf("Bug Report saved successfully)");
+        }
+        else {
+            a_http_simple->reply = dap_strdup_printf("Bug Report not saved(");
+        }
+        a_http_simple->reply_size = strlen(a_http_simple->reply);
+        *return_code = Http_Status_OK;
+
+    } else {
+        log_it(L_ERROR, "Wrong action '%s' for the request. Must be 'POST'", a_http_simple->http->action);
+        a_http_simple->reply = dap_strdup_printf("Wrong action '%s' for the request. Must be 'POST'",
+                a_http_simple->http->action);
+        a_http_simple->reply_size = strlen(a_http_simple->reply);
+        *return_code = Http_Status_NotFound;
+    }
+}
+
+/**
+ * @brief dap_chain_net_bugreport_add_proc
+ * @param sh HTTP server instance
+ */
+void dap_chain_net_bugreport_add_proc(struct dap_http * sh)
+{
+    const char * url = BUGREPORT_URL;
+    dap_http_simple_proc_add(sh, url, 14096, bugreport_http_proc);
+}
+
diff --git a/libdap-chain-net/dap_chain_net_bugreport.h b/libdap-chain-net/dap_chain_net_bugreport.h
new file mode 100644
index 0000000000000000000000000000000000000000..00f4c2d9f6d8d5271b8f80a76591e107232a1020
--- /dev/null
+++ b/libdap-chain-net/dap_chain_net_bugreport.h
@@ -0,0 +1,27 @@
+/*
+ * Authors:
+ * Alexander Lysikov <alexander.lysikov@demlabs.net>
+ * DeM Labs Inc.   https://demlabs.net
+ * Kelvin Project https://github.com/kelvinblockchain
+ * Copyright  (c) 2020
+ * All rights reserved.
+
+ This file is part of DAP (Deus Applications Prototypes) the open source project
+
+ DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ DAP is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with any DAP based project.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "dap_http.h"
+
+void dap_chain_net_bugreport_add_proc(struct dap_http * sh);
diff --git a/libdap-chain-net/dap_chain_net_remote.c b/libdap-chain-net/dap_chain_net_remote.c
new file mode 100644
index 0000000000000000000000000000000000000000..65ecddbf07ec61c836dcb3f52cca6c7c35d12f88
--- /dev/null
+++ b/libdap-chain-net/dap_chain_net_remote.c
@@ -0,0 +1,27 @@
+/*
+* Authors:
+* Dmitriy Gerasimov <naeper@demlabs.net>
+* Cellframe       https://cellframe.net
+* DeM Labs Inc.   https://demlabs.net
+* Copyright  (c) 2017-2019
+* All rights reserved.
+
+This file is part of CellFrame SDK the open source project
+
+CellFrame SDK is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+CellFrame SDK is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with any CellFrame SDK based project.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include "dap_common.h"
+#include "dap_chain_net_remote.h"
+
+#define LOG_TAG "dap_chain_net_remote"
diff --git a/libdap-chain-net/dap_chain_net_remote.h b/libdap-chain-net/dap_chain_net_remote.h
new file mode 100644
index 0000000000000000000000000000000000000000..e3e35be850eb3ec77cc4bc2d27ee4867abc52230
--- /dev/null
+++ b/libdap-chain-net/dap_chain_net_remote.h
@@ -0,0 +1,31 @@
+/*
+* Authors:
+* Dmitriy Gerasimov <naeper@demlabs.net>
+* Cellframe       https://cellframe.net
+* DeM Labs Inc.   https://demlabs.net
+* Copyright  (c) 2017-2019
+* All rights reserved.
+
+This file is part of CellFrame SDK the open source project
+
+CellFrame SDK is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+CellFrame SDK is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with any CellFrame SDK based project.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#pragma once
+
+#include "dap_chain_common.h"
+
+typedef struct dap_chain_net_remote {
+    dap_chain_node_addr_t remote_addr;
+    char                  remote_channel;
+} dap_chain_net_remote_t;
diff --git a/libdap-chain-net/dap_chain_node.c b/libdap-chain-net/dap_chain_node.c
new file mode 100644
index 0000000000000000000000000000000000000000..1f95a00da8600135fe02c8bcdb3db84648405c5a
--- /dev/null
+++ b/libdap-chain-net/dap_chain_node.c
@@ -0,0 +1,329 @@
+/*
+ * Authors:
+ * Dmitriy A. Gerasimov <naeper@demlabs.net>
+ * DeM Labs Inc.   https://demlabs.net
+
+ This file is part of DAP (Deus Applications Prototypes) the open source project
+
+ DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ DAP is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with any DAP based project.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#ifdef WIN32
+#include <winsock2.h>
+#include <windows.h>
+#include <mswsock.h>
+#include <ws2tcpip.h>
+#include <io.h>
+#include <pthread.h>
+#else
+#include <sys/socket.h>
+#include <netinet/in.h>
+#endif
+
+#include "utlist.h"
+#include "dap_hash.h"
+#include "rand/dap_rand.h"
+#include "dap_chain_net.h"
+#include "dap_chain_global_db.h"
+#include "dap_chain_node.h"
+
+#define LOG_TAG "chain_node"
+
+/**
+ * Generate node address by shard id
+ */
+dap_chain_node_addr_t* dap_chain_node_gen_addr(dap_chain_net_t * a_net,dap_chain_cell_id_t *shard_id)
+{
+    if(!shard_id)
+        return NULL;
+    dap_chain_node_addr_t *a_addr = DAP_NEW_Z(dap_chain_node_addr_t);
+    dap_chain_hash_fast_t a_hash;
+    dap_hash_fast(shard_id, sizeof(dap_chain_cell_id_t), &a_hash);
+    // first 4 bytes is last 4 bytes of shard id hash
+    memcpy(a_addr->raw, a_hash.raw + sizeof(a_hash.raw) - sizeof(uint64_t) / 2, sizeof(uint64_t) / 2);
+    // last 4 bytes is random
+    randombytes(a_addr->raw + sizeof(uint64_t) / 2, sizeof(uint64_t) / 2);
+    // for LITTLE_ENDIAN (Intel), do nothing, otherwise swap bytes
+    a_addr->uint64 = le64toh(a_addr->uint64); // a_addr->raw the same a_addr->uint64
+    return a_addr;
+}
+
+/**
+ * Check the validity of the node address by cell id
+ */
+bool dap_chain_node_check_addr(dap_chain_net_t * a_net,dap_chain_node_addr_t *addr, dap_chain_cell_id_t *shard_id)
+{
+    bool ret = false;
+    if(!addr || !shard_id)
+        ret= false;
+
+    return ret;
+}
+
+/**
+ * Register alias in base
+ */
+bool dap_chain_node_alias_register(dap_chain_net_t * a_net,const char *alias, dap_chain_node_addr_t *addr)
+{
+    char *a_key = strdup(alias);
+    dap_chain_node_addr_t *l_addr = DAP_NEW_Z(dap_chain_node_addr_t);
+    memcpy(l_addr,addr,sizeof (*l_addr));
+//    char a_value[2 * sizeof(dap_chain_node_addr_t) + 1];
+//    if(bin2hex(a_value, (const unsigned char *) addr, sizeof(dap_chain_node_addr_t)) == -1)
+//        return false;
+//    a_value[2 * sizeof(dap_chain_node_addr_t)] = '\0';
+    bool res = dap_chain_global_db_gr_set( dap_strdup(a_key),  l_addr, sizeof(dap_chain_node_addr_t)
+                                          , a_net->pub.gdb_nodes_aliases);
+    DAP_DELETE(l_addr);
+    DAP_DELETE(a_key);
+    return res;
+}
+
+/**
+ * @brief dap_chain_node_alias_find
+ * @param alias
+ * @return
+ */
+dap_chain_node_addr_t * dap_chain_node_alias_find(dap_chain_net_t * a_net,const char *a_alias)
+{
+    size_t l_addr_size =0;
+    dap_chain_node_addr_t * l_addr = (dap_chain_node_addr_t *)
+            dap_chain_global_db_gr_get(a_alias, &l_addr_size, a_net->pub.gdb_nodes_aliases);
+    return  l_addr;
+}
+
+/**
+ * Delete alias from base
+ */
+bool dap_chain_node_alias_delete(dap_chain_net_t * a_net,const char *a_alias)
+{
+    char *a_key = strdup(a_alias);
+    bool res = dap_chain_global_db_gr_del(a_key, a_net->pub.gdb_nodes_aliases);
+    return res;
+}
+
+/**
+ * Calculate size of struct dap_chain_node_info_t
+ */
+size_t dap_chain_node_info_get_size(dap_chain_node_info_t *node_info)
+{
+    if(!node_info)
+        return 0;
+    return (sizeof(dap_chain_node_info_t) + node_info->hdr.links_number * sizeof(dap_chain_node_addr_t));
+}
+
+/**
+ * @brief dap_chain_node_info_save
+ * @param node_info
+ * @return
+ */
+int dap_chain_node_info_save(dap_chain_net_t * a_net, dap_chain_node_info_t *a_node_info)
+{
+    if(!a_node_info || !a_node_info->hdr.address.uint64){
+        log_it(L_ERROR,"Can't save node info: %s", a_node_info? "null address":"null object" );
+        return  -1;
+    }
+    char *l_key = dap_chain_node_addr_to_hash_str(&a_node_info->hdr.address);
+
+    if(!l_key){
+        log_it(L_ERROR,"Can't produce key to save node info ");
+        return -2;
+    }
+    //char *a_value = dap_chain_node_info_serialize(node_info, NULL);
+    size_t l_node_info_size = dap_chain_node_info_get_size(a_node_info);
+    dap_chain_node_info_t *l_node_info = DAP_NEW_Z_SIZE(dap_chain_node_info_t, l_node_info_size);
+    memcpy(l_node_info, a_node_info, sizeof (*a_node_info) );
+    bool res = dap_chain_global_db_gr_set( dap_strdup(l_key), l_node_info, l_node_info_size, a_net->pub.gdb_nodes);
+    DAP_DELETE(l_key);
+    //DAP_DELETE(a_value);
+    return res?0:-3;
+}
+
+/**
+ * Read node from base
+ */
+dap_chain_node_info_t* dap_chain_node_info_read( dap_chain_net_t * a_net,dap_chain_node_addr_t *l_address)
+{
+    char *l_key = dap_chain_node_addr_to_hash_str(l_address);
+    if(!l_key) {
+        log_it(L_WARNING,"Can't calculate hash of addr");
+        return NULL;
+    }
+    size_t node_info_size = 0;
+    dap_chain_node_info_t *l_node_info;
+    // read node
+    l_node_info = (dap_chain_node_info_t *) dap_chain_global_db_gr_get(l_key, &node_info_size, a_net->pub.gdb_nodes);
+
+    if(!l_node_info) {
+        log_it(L_ERROR, "node with key %s (addr " NODE_ADDR_FP_STR ") not found in base",l_key, NODE_ADDR_FP_ARGS(l_address));
+        DAP_DELETE(l_key);
+        return NULL;
+    }
+
+    size_t node_info_size_must_be = dap_chain_node_info_get_size(l_node_info);
+    if(node_info_size_must_be != node_info_size) {
+        log_it(L_ERROR, "Node has bad size in base=%u (must be %u)", node_info_size, node_info_size_must_be);
+        DAP_DELETE(l_node_info);
+        DAP_DELETE(l_key);
+        return NULL;
+    }
+
+//    dap_chain_node_info_t *node_info = dap_chain_node_info_deserialize(str, (str) ? strlen(str) : 0);
+//    if(!node_info) {
+//        set_reply_text(str_reply, "node has invalid format in base");
+//    }
+//    DAP_DELETE(str);
+    DAP_DELETE(l_key);
+    return l_node_info;
+}
+
+
+/**
+ * Serialize dap_chain_node_info_t
+ * size[out] - length of output string
+ * return data or NULL if error
+ */
+/*uint8_t* dap_chain_node_info_serialize(dap_chain_node_info_t *node_info, size_t *size)
+{
+    if(!node_info)
+        return NULL;
+    size_t node_info_size = dap_chain_node_info_get_size(node_info);
+    size_t node_info_str_size = 2 * node_info_size + 1;
+    uint8_t *node_info_str = DAP_NEW_Z_SIZE(uint8_t, node_info_str_size);
+    if(bin2hex(node_info_str, (const unsigned char *) node_info, node_info_size) == -1) {
+        DAP_DELETE(node_info_str);
+        return NULL;
+    }
+
+    if(size)
+        *size = node_info_str_size;
+    return node_info_str;
+}*/
+
+/**
+ * Deserialize dap_chain_node_info_t
+ * size[in] - length of input string
+ * return data or NULL if error
+ */
+/*dap_chain_node_info_t* dap_chain_node_info_deserialize(uint8_t *node_info_str, size_t size)
+{
+    if(!node_info_str || size<=0)
+        return NULL;
+    dap_chain_node_info_t *node_info = DAP_NEW_Z_SIZE(dap_chain_node_info_t, (size / 2 + 1));
+    if(hex2bin((char*) node_info, (const unsigned char *) node_info_str, size) == -1 ||
+            (size / 2) != dap_chain_node_info_get_size(node_info)) {
+        log_it(L_ERROR, "node_info_deserialize - incorrect node_info size (%ld!=%ld)",
+                size / 2, dap_chain_node_info_get_size(node_info));
+        DAP_DELETE(node_info);
+        return NULL;
+    }
+    return node_info;
+}*/
+
+int dap_chain_node_mempool_process(dap_chain_t *a_chain)
+{
+    char *l_gdb_group_mempool = NULL;
+    if (!a_chain) {
+        return -1;
+    }
+    l_gdb_group_mempool = dap_chain_net_get_gdb_group_mempool(a_chain);
+    size_t l_objs_size = 0;
+    dap_global_db_obj_t *l_objs = dap_chain_global_db_gr_load(l_gdb_group_mempool, &l_objs_size);
+    if (l_objs_size) {
+        for (size_t i = 0; i < l_objs_size; i++) {
+            dap_chain_datum_t *l_datum = (dap_chain_datum_t *)l_objs[i].value;
+            if (l_datum->header.type_id != DAP_CHAIN_DATUM_TX) {
+                continue;
+            }
+            if (a_chain->callback_datums_pool_proc(a_chain, &l_datum, 1) != 1) {
+                continue;
+            } // Delete processed objects
+            dap_chain_global_db_gr_del(dap_strdup(l_objs[i].key), l_gdb_group_mempool);
+        }
+        dap_chain_global_db_objs_delete(l_objs, l_objs_size);
+    }
+    DAP_DELETE(l_gdb_group_mempool);
+    return 0;
+}
+
+void dap_chain_node_mempool_periodic(void *a_param)
+{
+    UNUSED(a_param);
+    size_t l_net_count;
+    bool l_mempool_auto;
+    dap_chain_net_t **l_net_list = dap_chain_net_list(&l_net_count);
+    for (int i = 0; i < l_net_count; i++) {
+        l_mempool_auto = dap_config_get_item_bool_default(g_config, "mempool", "auto_proc", false);
+        if (!l_mempool_auto) {
+            dap_chain_node_role_t l_role = dap_chain_net_get_role(l_net_list[i]);
+            switch (l_role.enums) {
+            case NODE_ROLE_ROOT:
+            case NODE_ROLE_MASTER:
+            case NODE_ROLE_ROOT_MASTER:
+            case NODE_ROLE_CELL_MASTER:
+                l_mempool_auto = true;
+                break;
+            default:
+                break;
+            }
+        }
+        if (l_mempool_auto) {
+            dap_chain_t *l_chain;
+            DL_FOREACH(l_net_list[i]->pub.chains, l_chain) {
+                for (uint16_t i = 0; i < l_chain->datum_types_count; i++) {
+                    if (l_chain->datum_types[i] == CHAIN_TYPE_TX) {
+                        dap_chain_node_mempool_process(l_chain);
+                    }
+                }
+            }
+        }
+    }
+    DAP_DELETE(l_net_list);
+}
+
+static void *s_mempool_timer = NULL;
+
+/**
+ * @brief dap_chain_node_mempool_init
+ * @return
+ */
+int dap_chain_node_mempool_init()
+{
+    s_mempool_timer = dap_interval_timer_create(DAP_CHAIN_NODE_MEMPOOL_INTERVAL, dap_chain_node_mempool_periodic, 0);
+    if (s_mempool_timer) {
+        return 0;
+    } else {
+        return -1;
+    }
+}
+
+/**
+ * @brief dap_chain_node_mempool_deinit
+ */
+void dap_chain_node_mempool_deinit()
+{
+    if (s_mempool_timer) {
+        dap_interval_timer_delete(s_mempool_timer);
+        s_mempool_timer = NULL;
+    }
+}
+
diff --git a/libdap-chain-net/dap_chain_node.h b/libdap-chain-net/dap_chain_node.h
new file mode 100644
index 0000000000000000000000000000000000000000..1e5f0b6d7f4fe06e8c98923151ca79c10a256ea5
--- /dev/null
+++ b/libdap-chain-net/dap_chain_node.h
@@ -0,0 +1,146 @@
+/*
+ * Authors:
+ * Dmitriy A. Gerasimov <naeper@demlabs.net>
+ * DeM Labs Inc.   https://demlabs.net
+
+ This file is part of DAP (Deus Applications Prototypes) the open source project
+
+    DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    DAP is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with any DAP based project.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#include <stdint.h>
+#include <stddef.h>
+#include <stdbool.h>
+
+#ifndef _WIN32
+#include <sys/socket.h>
+#include <netinet/in.h>
+#else
+#include <winsock2.h>
+#include <windows.h>
+#include <mswsock.h>
+#include <ws2tcpip.h>
+
+#include "win32/ip.h"
+#include "win32/iphdr.h"
+#endif
+
+
+#include "dap_common.h"
+#include "dap_chain_common.h"
+#include "dap_chain_global_db.h"
+#include "dap_chain.h"
+#include "dap_chain_net.h"
+
+typedef struct dap_chain_net dap_chain_net_t;
+/**
+  *  Node Declaration request
+  *
+  */
+
+#define DAP_CHAIN_NODE_DECL_REQ_INFO_SIZE 32
+typedef struct dap_chain_node_delc_req{
+    dap_chain_node_addr_t node_address;
+    uint64_t create_ts;
+    union{
+        uint8_t raw[DAP_CHAIN_NODE_DECL_REQ_INFO_SIZE];
+        char str[DAP_CHAIN_NODE_DECL_REQ_INFO_SIZE];
+    } info;
+} DAP_ALIGN_PACKED dap_chain_decl_req_t;
+
+/**
+  * @struct dap_chain_node decl
+  * @details New node declaration
+  *
+  */
+#define DAP_CHAIN_NODE_DECL_ACCEPT_INFO_SIZE 32
+typedef struct dap_chain_node_decl{
+    dap_chain_decl_req_t request;
+    uint64_t accept_ts;
+    struct in_addr accept_addr_v4;
+    struct in6_addr accept_addr_v6;
+    union{
+        uint8_t raw[DAP_CHAIN_NODE_DECL_ACCEPT_INFO_SIZE];
+        char str[DAP_CHAIN_NODE_DECL_ACCEPT_INFO_SIZE];
+    } accept_info;
+} DAP_ALIGN_PACKED dap_chain_node_decl_t;
+
+typedef struct dap_chain_node_info
+{
+    struct {
+        dap_chain_node_addr_t address;
+        dap_chain_cell_id_t cell_id;
+        uint32_t links_number;
+        struct in_addr ext_addr_v4;
+        struct in6_addr ext_addr_v6;
+        uint16_t ext_port; // Port thats node listening
+        char alias[256];
+    } DAP_ALIGN_PACKED hdr;
+    dap_chain_node_addr_t links[]; // dap_chain_addr_t
+} DAP_ALIGN_PACKED dap_chain_node_info_t;
+
+typedef struct dap_chain_node_publ{
+    dap_chain_hash_fast_t decl_hash;
+    dap_chain_node_info_t node_info;
+} DAP_ALIGN_PACKED dap_chain_node_publ_t;
+
+#define DAP_CHAIN_NODE_MEMPOOL_INTERVAL 1000    // milliseconds
+
+/**
+ * Calculate size of struct dap_chain_node_info_t
+ */
+size_t dap_chain_node_info_get_size(dap_chain_node_info_t *node_info);
+
+/**
+ * Serialize dap_chain_node_info_t
+ * size[out] - length of output string
+ * return data or NULL if error
+ */
+//uint8_t* dap_chain_node_info_serialize(dap_chain_node_info_t *node_info, size_t *size);
+
+/**
+ * Deserialize dap_chain_node_info_t
+ * size[in] - length of input string
+ * return data or NULL if error
+ */
+//dap_chain_node_info_t* dap_chain_node_info_deserialize(uint8_t *node_info_str, size_t size);
+
+/**
+ * Generate node addr by shard id
+ */
+dap_chain_node_addr_t* dap_chain_node_gen_addr(dap_chain_net_t * l_net,dap_chain_cell_id_t *a_cell_id);
+
+/**
+ * Check the validity of the node address by shard id
+ */
+bool dap_chain_node_check_addr(dap_chain_net_t * l_net,dap_chain_node_addr_t *addr, dap_chain_cell_id_t *a_cell_id);
+
+dap_chain_node_addr_t * dap_chain_node_alias_find(dap_chain_net_t * l_net,const char *alias);
+bool dap_chain_node_alias_register(dap_chain_net_t * l_net,const char *alias, dap_chain_node_addr_t *addr);
+bool dap_chain_node_alias_delete(dap_chain_net_t * l_net,const char *alias);
+
+int dap_chain_node_info_save(dap_chain_net_t * l_net,dap_chain_node_info_t *node_info);
+dap_chain_node_info_t* dap_chain_node_info_read(dap_chain_net_t * l_net, dap_chain_node_addr_t *address);
+
+inline static char* dap_chain_node_addr_to_hash_str(dap_chain_node_addr_t *address)
+{
+    char *a_key = dap_chain_global_db_hash((const uint8_t*) address, sizeof(dap_chain_node_addr_t));
+    return a_key;
+}
+
+int dap_chain_node_mempool_process(dap_chain_t *a_chain);
+int dap_chain_node_mempool_init();
+void dap_chain_node_mempool_deinit();
diff --git a/libdap-chain-net/dap_chain_node_cli.c b/libdap-chain-net/dap_chain_node_cli.c
new file mode 100644
index 0000000000000000000000000000000000000000..ddbf5bdb16ee72b539f02eea05e1819313c343b2
--- /dev/null
+++ b/libdap-chain-net/dap_chain_node_cli.c
@@ -0,0 +1,1139 @@
+/*
+ * Authors:
+ * Dmitriy A. Gerasimov <gerasimov.dmitriy@demlabs.net>
+ * Alexander Lysikov <alexander.lysikov@demlabs.net>
+ * DeM Labs Inc.   https://demlabs.net
+ * Kelvin Project https://github.com/kelvinblockchain
+ * Copyright  (c) 2019
+ * All rights reserved.
+
+ This file is part of DAP (Deus Applications Prototypes) the open source project
+
+ DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ DAP is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with any DAP based project.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+//#include <glib.h>
+
+#ifndef _WIN32
+#include <poll.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <arpa/inet.h>
+//#include <unistd.h> // for close
+#include <fcntl.h>
+//#include <sys/poll.h>
+//#include <sys/select.h>
+#include <netinet/in.h>
+#include <sys/un.h>
+//#define closesocket close
+//typedef int SOCKET;
+//#define SOCKET_ERROR    -1  // for win32 =  (-1)
+//#define INVALID_SOCKET  -1  // for win32 =  (SOCKET)(~0)
+// for Windows
+#else
+#include <winsock2.h>
+#include <windows.h>
+#include <mswsock.h>
+#include <ws2tcpip.h>
+#include <io.h>
+#endif
+
+#include <pthread.h>
+
+#include "iputils/iputils.h"
+#include "dap_common.h"
+#include "dap_config.h"
+#include "dap_strfuncs.h"
+#include "dap_list.h"
+#include "dap_chain_node_cli_cmd.h"
+#include "dap_chain_node_client.h"
+#include "dap_chain_node_cli.h"
+
+//#include "dap_chain_node_cli.h"
+
+#define LOG_TAG "chain_node_cli"
+
+#define MAX_CONSOLE_CLIENTS 16
+
+static SOCKET server_sockfd = -1; // network or local unix
+uint32_t l_listen_port = 0;
+
+#ifdef _WIN32
+  #define poll WSAPoll
+#endif
+
+static dap_chain_node_cmd_item_t * s_commands = NULL;
+
+
+/**
+ * Wait for data
+ * timeout -  timeout in ms
+ * [Specifying a negative value in timeout means an infinite timeout.]
+ * [Specifying a timeout of zero causes poll() to return immediately, even if no file descriptors are ready.]
+ * return zero if the time limit expired
+ * return: >0 if data is present to read
+ * return: -1 if error
+ */
+static int s_poll( int socket, int timeout )
+{
+    struct pollfd fds;
+    int res;
+    fds.fd = socket;
+    // POLLIN - received data
+    // POLLNVAL - closed the socket on our side
+    // POLLHUP - closed the socket on another side (does not work! Received POLLIN and the next reading returns 0 bytes)
+    fds.events = POLLIN; // | | POLLNVAL | POLLHUP | POLLERR | POLLPRI
+    res = poll(&fds, 1, timeout);
+
+    // since POLLIN=(POLLRDNORM | POLLRDBAND), then maybe revents=POLLRDNORM
+    if(res == 1 && !(fds.revents & POLLIN)) //if(res==1 && fds.revents!=POLLIN)
+        return -1;
+    return res;
+}
+
+/**
+ * Check socket for validity
+ */
+static bool is_valid_socket(SOCKET sock)
+{
+    struct pollfd fds;
+    fds.fd = sock;
+    fds.events = POLLIN;
+    // return: -1 err, 0 timeout, 1 waited
+    int count_desc = poll(&fds, 1, 0);
+    // error
+    if(count_desc == -1)
+        return false;
+    // event with an error code
+    if(count_desc > 0)
+            {
+        // feature of disconnection under Windows
+        // under Windows, with socket closed fds.revents=POLLHUP, in Unix fds.events = POLLIN
+        if(fds.revents & (POLLERR | POLLHUP | POLLNVAL))
+            return false;
+        // feature of disconnection under Unix (QNX)
+        // under Windows, with socket closed res = 0, in Unix res = -1
+        char buf[2];
+        long res = recv(sock, buf, 1, MSG_PEEK); // MSG_PEEK  The data is treated as unread and the next recv() function shall still return this data.
+        if(res < 0)
+            return false;
+        // data in the buffer must be(count_desc>0), but read 0 bytes(res=0)
+        if(!res && (fds.revents & POLLIN))
+            return false;
+    }
+    return true;
+}
+
+/**
+ * Read from socket
+ *
+ * timeout in milliseconds
+ * return the number of read bytes (-1 err or -2 timeout)
+ */
+long s_recv(SOCKET sock, unsigned char *buf, size_t bufsize, int timeout)
+{
+    struct pollfd fds;
+    long res;
+    fds.fd = sock;
+    fds.events = POLLIN; // | POLLNVAL | POLLHUP | POLLERR | POLLPRI;// | POLLRDNORM;//POLLOUT |
+    res = poll(&fds, 1, timeout);
+    if(res == 1 && !(fds.revents & POLLIN))
+        return -1;
+    if(!res) // timeout
+        return -2;
+    if(res < 1) {
+        return -1;
+    }
+    //    res = read(sock, (char*) buf, bufsize);
+    res = recv(sock, (char*) buf, bufsize, 0); //MSG_WAITALL
+    if(res <= 0) { //EINTR=4  ENOENT=2 EINVAL=22 ECONNRESET=254
+        printf("[s_recv] recv()=%ld errno=%d\n", res, errno);
+    }
+    return res;
+}
+
+/**
+ * Reading from the socket till arrival the specified string
+ *
+ * stop_str - string to which reading will continue
+ * del_stop_str - удалять ли строку для поиска в конце
+ * timeout - in ms
+ * return: string (if waited for final characters) or NULL, if the string requires deletion
+ */
+char* s_get_next_str( SOCKET nSocket, int *dwLen, const char *stop_str, bool del_stop_str, int timeout )
+{
+    bool bSuccess = false;
+    long nRecv = 0; // count of bytes received
+    size_t stop_str_len = (stop_str) ? strlen(stop_str) : 0;
+    // if there is nothing to look for
+    if(!stop_str_len)
+        return NULL;
+    size_t lpszBuffer_len = 256;
+    char *lpszBuffer = DAP_NEW_Z_SIZE(char, lpszBuffer_len);
+    // received string will not be larger than MAX_REPLY_LEN
+
+    while(1) //nRecv < MAX_REPLY_LEN)
+    {
+        // read one byte
+        long ret = s_recv(nSocket, (unsigned char *) (lpszBuffer + nRecv), 1, timeout);
+        //int ret = recv(nSocket,lpszBuffer+nRecv,1, 0);
+        if(ret <= 0)
+                {
+            break;
+        }
+        nRecv += ret;
+        //printf("**debug** socket=%d read  %d bytes '%0s'",nSocket, ret, (lpszBuffer + nRecv));
+        while((nRecv + 1) >= (long) lpszBuffer_len)
+        {
+            lpszBuffer_len *= 2;
+            lpszBuffer = (char*) realloc(lpszBuffer, lpszBuffer_len);
+        }
+        // search for the required string
+        if(nRecv >=  (long) stop_str_len) {
+            // found the required string
+            if(!strncasecmp(lpszBuffer + nRecv - stop_str_len, stop_str, stop_str_len)) {
+                bSuccess = true;
+                break;
+            }
+        }
+    };
+
+    // end reading
+
+    if(bSuccess) {
+        // delete the searched string
+        if(del_stop_str) {
+            lpszBuffer[nRecv -  (long) stop_str_len] = '\0';
+            if(dwLen)
+                *dwLen =(int) nRecv - (int) stop_str_len;
+        }
+        else {
+            lpszBuffer[nRecv] = '\0';
+            if(dwLen)
+                *dwLen = (int) nRecv;
+        }
+        lpszBuffer = DAP_REALLOC(lpszBuffer,(size_t) *dwLen + 1);
+        return lpszBuffer;
+    }
+
+    // in case of an error or missing string
+
+    if(dwLen)
+        *dwLen = 0;
+
+    free(lpszBuffer);
+
+    return NULL;
+}
+
+/**
+ * threading function for processing a request from a client
+ */
+static void* thread_one_client_func(void *args)
+{
+    SOCKET newsockfd = (SOCKET) (intptr_t) args;
+    log_it(L_INFO, "new connection sockfd=%d", newsockfd);
+
+    int str_len, marker = 0;
+    int timeout = 5000; // 5 sec
+    int argc = 0;
+    dap_list_t *cmd_param_list = NULL;
+    while(1)
+    {
+        // wait data from client
+        int is_data = s_poll(newsockfd, timeout);
+        // timeout
+        if(!is_data)
+            continue;
+        // error (may be socket closed)
+        if(is_data < 0)
+            break;
+
+        int is_valid = is_valid_socket(newsockfd);
+        if(!is_valid)
+        {
+            break;
+        }
+        // receiving http header
+        char *str_header = s_get_next_str(newsockfd, &str_len, "\r\n", true, timeout);
+        // bad format
+        if(!str_header)
+            break;
+        if(str_header && strlen(str_header) == 0) {
+            marker++;
+            if(marker == 1)
+                continue;
+        }
+        // filling parameters of command
+        if(marker == 1) {
+            cmd_param_list = dap_list_append(cmd_param_list, str_header);
+            //printf("g_list_append argc=%d command=%s ", argc, str_header);
+            argc++;
+        }
+        else
+            free(str_header);
+        if(marker == 2) {
+            dap_list_t *list = cmd_param_list;
+            // form command
+            unsigned int argc = dap_list_length(list);
+            // command is found
+            if(argc >= 1) {
+              int l_verbose = 0;
+                char *cmd_name = list->data;
+                list = dap_list_next(list);
+                // execute command
+                char *str_cmd = dap_strdup_printf("%s", cmd_name);
+                dap_chain_node_cmd_item_t *l_cmd = dap_chain_node_cli_cmd_find(cmd_name);
+                int res = -1;
+                char *str_reply = NULL;
+                if(l_cmd){
+                    while(list) {
+                        char *str_cmd_prev = str_cmd;
+                        str_cmd = dap_strdup_printf("%s;%s", str_cmd, list->data);
+                        list = dap_list_next(list);
+                        DAP_DELETE(str_cmd_prev);
+                    }
+                    log_it(L_INFO, "execute command=%s", str_cmd);
+                    // exec command
+
+                    char **l_argv = dap_strsplit(str_cmd, ";", -1);
+                    // Call the command function
+                    if(l_cmd &&  l_argv && l_cmd->func)
+                        res = (*(l_cmd->func))(argc, l_argv, l_cmd->arg_func, &str_reply);
+                    else if (l_cmd){
+                        log_it(L_WARNING,"NULL arguments for input for command \"%s\"", str_cmd);
+                    }else {
+                        log_it(L_WARNING,"No function for command \"%s\" but it registred?!", str_cmd);
+                    }
+                    // find '-verbose' command
+                    l_verbose = dap_chain_node_cli_find_option_val(l_argv, 1, argc, "-verbose", NULL);
+                    dap_strfreev(l_argv);
+                } else {
+                    str_reply = dap_strdup_printf("can't recognize command=%s", str_cmd);
+                    log_it(L_ERROR, str_reply);
+                }
+                char *reply_body;
+                if(l_verbose)
+                  reply_body = dap_strdup_printf("%d\r\nret_code: %d\r\n%s\r\n", res, res, (str_reply) ? str_reply : "");
+                else
+                  reply_body = dap_strdup_printf("%d\r\n%s\r\n", res, (str_reply) ? str_reply : "");
+                // return the result of the command function
+                char *reply_str = dap_strdup_printf("HTTP/1.1 200 OK\r\n"
+                                                    "Content-Length: %d\r\n\r\n"
+                                                    "%s",
+                        strlen(reply_body), reply_body);
+                /*int ret = */ send(newsockfd, reply_str, strlen(reply_str) ,0);
+                DAP_DELETE(str_reply);
+                DAP_DELETE(reply_str);
+                DAP_DELETE(reply_body);
+
+                DAP_DELETE(str_cmd);
+            }
+            dap_list_free_full(cmd_param_list, free);
+            break;
+        }
+    }
+    // close connection
+    int cs = closesocket(newsockfd);
+    log_it(L_INFO, "close connection=%d sockfd=%d", cs, newsockfd);
+    return NULL;
+}
+
+#ifdef _WIN32
+
+char *p_get_next_str( HANDLE hPipe, int *dwLen, const char *stop_str, bool del_stop_str, int timeout )
+{
+    bool bSuccess = false;
+    long nRecv = 0; // count of bytes received
+    size_t stop_str_len = (stop_str) ? strlen(stop_str) : 0;
+    // if there is nothing to look for
+
+    if(!stop_str_len)
+        return NULL;
+
+    size_t lpszBuffer_len = 256;
+    char *lpszBuffer = DAP_NEW_Z_SIZE(char, lpszBuffer_len);
+    // received string will not be larger than MAX_REPLY_LEN
+
+    while( 1 ) //nRecv < MAX_REPLY_LEN)
+    {
+      long ret = 0;
+        // read one byte
+//        long ret = s_recv( nSocket, (unsigned char *) (lpszBuffer + nRecv), 1, timeout);
+
+      bSuccess = ReadFile( hPipe, lpszBuffer + nRecv,
+         lpszBuffer_len - nRecv, (LPDWORD)&ret, NULL );
+
+        //int ret = recv(nSocket,lpszBuffer+nRecv,1, 0);
+        if ( ret <= 0 || !bSuccess )
+            break;
+
+        nRecv += ret;
+        //printf("**debug** socket=%d read  %d bytes '%0s'",nSocket, ret, (lpszBuffer + nRecv));
+
+        while((nRecv + 1) >= (long) lpszBuffer_len)
+        {
+            lpszBuffer_len *= 2;
+            lpszBuffer = (char*) realloc(lpszBuffer, lpszBuffer_len);
+        }
+
+        // search for the required string
+        if(nRecv >=  (long) stop_str_len) {
+            // found the required string
+            if(!strncasecmp(lpszBuffer + nRecv - stop_str_len, stop_str, stop_str_len)) {
+                bSuccess = true;
+                break;
+            }
+        }
+    };
+
+    // end reading
+
+    if(bSuccess) {
+        // delete the searched string
+        if(del_stop_str) {
+            lpszBuffer[nRecv -  (long) stop_str_len] = '\0';
+            if(dwLen)
+                *dwLen =(int) nRecv - (int) stop_str_len;
+        }
+        else {
+            lpszBuffer[nRecv] = '\0';
+            if(dwLen)
+                *dwLen = (int) nRecv;
+        }
+        lpszBuffer = DAP_REALLOC(lpszBuffer,(size_t) *dwLen + 1);
+        return lpszBuffer;
+    }
+
+    // in case of an error or missing string
+
+    if(dwLen)
+        *dwLen = 0;
+
+    free(lpszBuffer);
+
+    return NULL;
+}
+
+
+/**
+ * threading function for processing a request from a client
+ */
+static void *thread_pipe_client_func( void *args )
+{
+    HANDLE hPipe = (HANDLE)args;
+
+//    SOCKET newsockfd = (SOCKET) (intptr_t) args;
+    log_it(L_INFO, "new connection pipe = %X", hPipe );
+
+    int str_len, marker = 0;
+    int timeout = 5000; // 5 sec
+    int argc = 0;
+
+    dap_list_t *cmd_param_list = NULL;
+
+    while( 1 )
+    {
+        // wait data from client
+//        int is_data = s_poll( newsockfd, timeout );
+        // timeout
+//        if(!is_data)
+//            continue;
+        // error (may be socket closed)
+//        if(is_data < 0)
+//            break;
+
+//        int is_valid = is_valid_socket(newsockfd);
+//        if(!is_valid)
+//        {
+//            break;
+//        }
+
+        // receiving http header
+        char *str_header = p_get_next_str( hPipe, &str_len, "\r\n", true, timeout );
+
+        // bad format
+        if(!str_header)
+            break;
+
+        if ( str_header && strlen(str_header) == 0) {
+            marker++;
+            if(marker == 1)
+                continue;
+        }
+
+        // filling parameters of command
+        if ( marker == 1 ) {
+            cmd_param_list = dap_list_append( cmd_param_list, str_header );
+            //printf("g_list_append argc=%d command=%s ", argc, str_header);
+            argc ++;
+        }
+        else
+            free( str_header );
+
+        if ( marker == 2 ) {
+
+            dap_list_t *list = cmd_param_list;
+            // form command
+
+            unsigned int argc = dap_list_length( list );
+            // command is found
+
+            if ( argc >= 1) {
+
+                int l_verbose = 0;
+                char *cmd_name = list->data;
+                list = dap_list_next( list );
+
+                // execute command
+                char *str_cmd = dap_strdup_printf( "%s", cmd_name );
+                dap_chain_node_cmd_item_t *l_cmd = dap_chain_node_cli_cmd_find( cmd_name );
+                int res = -1;
+                char *str_reply = NULL;
+
+                if ( l_cmd ) {
+
+                    while( list ) {
+                        str_cmd = dap_strdup_printf( "%s;%s", str_cmd, list->data );
+                        list = dap_list_next(list);
+                    }
+
+                    log_it(L_INFO, "execute command = %s", str_cmd );
+                    // exec command
+
+                    char **l_argv = dap_strsplit( str_cmd, ";", -1 );
+                    // Call the command function
+
+                    if ( l_cmd &&  l_argv && l_cmd->func )
+                        res = (* (l_cmd->func))( argc, l_argv, NULL, &str_reply );
+
+                    else if ( l_cmd ) {
+                        log_it(L_WARNING,"NULL arguments for input for command \"%s\"", str_cmd );
+                    }else {
+                        log_it(L_WARNING,"No function for command \"%s\" but it registred?!", str_cmd );
+                    }
+
+                    // find '-verbose' command
+                    l_verbose = dap_chain_node_cli_find_option_val( l_argv, 1, argc, "-verbose", NULL );
+                    dap_strfreev( l_argv );
+
+                } else {
+                    str_reply = dap_strdup_printf("can't recognize command = %s", str_cmd );
+                    log_it( L_ERROR, str_reply );
+                }
+
+                char *reply_body;
+
+                if(l_verbose)
+                  reply_body = dap_strdup_printf("%d\r\nret_code: %d\r\n%s\r\n", res, res, (str_reply) ? str_reply : "");
+                else
+                  reply_body = dap_strdup_printf("%d\r\n%s\r\n", res, (str_reply) ? str_reply : "");
+
+                // return the result of the command function
+                char *reply_str = dap_strdup_printf( "HTTP/1.1 200 OK\r\n"
+                                                    "Content-Length: %d\r\n\r\n"
+                                                    "%s",
+                        strlen(reply_body), reply_body );
+
+                int ret;// = send( newsockfd, reply_str, strlen(reply_str) ,0 );
+
+                WriteFile( hPipe, reply_str, strlen(reply_str), (LPDWORD)&ret, NULL );
+
+                DAP_DELETE(str_reply);
+                DAP_DELETE(reply_str);
+                DAP_DELETE(reply_body);
+
+                DAP_DELETE(str_cmd);
+            }
+            dap_list_free_full(cmd_param_list, free);
+            break;
+        }
+    }
+
+    // close connection
+//    int cs = closesocket(newsockfd);
+
+    log_it( L_INFO, "close connection pipe = %X", hPipe );
+
+    FlushFileBuffers( hPipe );
+    DisconnectNamedPipe( hPipe );
+    CloseHandle( hPipe );
+
+    return NULL;
+}
+
+
+/**
+ * main threading server function pipe win32
+ */
+static void* thread_pipe_func( void *args )
+{
+   BOOL   fConnected = FALSE;
+   pthread_t threadId;
+   HANDLE hPipe = INVALID_HANDLE_VALUE, hThread = NULL;
+   static const char *cPipeName = "\\\\.\\pipe\\node_cli.pipe";
+
+   for (;;)
+   {
+///      printf( "\nPipe Server: Main thread awaiting client connection on %s\n", lpszPipename );
+
+      hPipe = CreateNamedPipe(
+          cPipeName,                // pipe name
+          PIPE_ACCESS_DUPLEX,       // read/write access
+          PIPE_TYPE_MESSAGE |       // message type pipe
+          PIPE_READMODE_MESSAGE |   // message-read mode
+          PIPE_WAIT,                // blocking mode
+          PIPE_UNLIMITED_INSTANCES, // max. instances
+          4096,                     // output buffer size
+          4096,                     // input buffer size
+          0,                        // client time-out
+          NULL );                   // default security attribute
+
+      if ( hPipe == INVALID_HANDLE_VALUE ) {
+          log_it( L_ERROR, "CreateNamedPipe failed, GLE = %d.\n", GetLastError() );
+          return NULL;
+      }
+
+      fConnected = ConnectNamedPipe( hPipe, NULL ) ? TRUE : ( GetLastError() == ERROR_PIPE_CONNECTED );
+
+      if ( fConnected )
+      {
+        log_it( L_INFO, "Client %X connected, creating a processing thread.\n", hPipe );
+
+        pthread_create( &threadId, NULL, thread_pipe_client_func, hPipe );
+        pthread_detach( threadId );
+      }
+      else
+         CloseHandle( hPipe );
+    }
+
+    return NULL;
+}
+#endif
+
+/**
+ * main threading server function
+ */
+static void* thread_main_func(void *args)
+{
+    SOCKET sockfd = (SOCKET) (intptr_t) args;
+    SOCKET newsockfd;
+
+    log_it( L_INFO, "Server start socket = %s", dap_config_get_item_str( g_config, "conserver", "listen_unix_socket_path") );
+    // wait of clients
+    while(1)
+    {
+        pthread_t threadId;
+        struct sockaddr_in peer;
+        socklen_t size = sizeof(peer);
+        // received a new connection request
+        if((newsockfd = accept(sockfd, (struct sockaddr*) &peer, &size)) == (SOCKET) -1) {
+            log_it(L_ERROR, "new connection break newsockfd=%d", newsockfd);
+            break;
+        }
+        // create child thread for a client connection
+        pthread_create(&threadId, NULL, thread_one_client_func, (void*) (intptr_t) newsockfd);
+        // in order to thread not remain in state "dead" after completion
+        pthread_detach(threadId);
+    };
+    // close connection
+    int cs = closesocket(sockfd);
+    log_it(L_INFO, "Exit server thread=%d socket=%d", cs, sockfd);
+    return NULL;
+}
+
+/**
+ * Write text to reply string
+ */
+void dap_chain_node_cli_set_reply_text(char **str_reply, const char *str, ...)
+{
+    if(str_reply) {
+        if(*str_reply) {
+            assert(! *str_reply );
+            DAP_DELETE(*str_reply);
+            *str_reply = NULL;
+        }
+        va_list args;
+        va_start(args, str);
+        *str_reply = dap_strdup_vprintf(str, args); //*str_reply = dap_strdup(str);
+        va_end(args);
+    }
+}
+
+/**
+ * find option value
+ *
+ * return index of string in argv, or 0 if not found
+ */
+int dap_chain_node_cli_find_option_val( char** argv, int arg_start, int arg_end, const char *opt_name, const char **opt_value)
+{
+    int arg_index = arg_start;
+    const char *arg_string;
+
+    while(arg_index < arg_end)
+    {
+        char * l_argv_cur = argv[arg_index];
+        arg_string = l_argv_cur;
+        // find opt_name
+        if(arg_string && opt_name && arg_string[0] && opt_name[0] && !strcmp(arg_string, opt_name)) {
+            // find opt_value
+            if(opt_value) {
+                arg_string = argv[++arg_index];
+                if(arg_string) {
+                    *opt_value = arg_string;
+                    return arg_index;
+                }
+            }
+            else
+                // need only opt_name
+                return arg_index;
+        }
+        arg_index++;
+    }
+    return 0;
+}
+
+/**
+ * @brief s_cmd_item_create
+ * @param a_name
+ * @param func
+ * @param a_arg_func
+ * @param doc
+ * @param doc_ex
+ * @return
+ */
+void dap_chain_node_cli_cmd_item_create(const char * a_name, cmdfunc_t *a_func, void *a_arg_func, const char *a_doc, const char *a_doc_ex)
+{
+    dap_chain_node_cmd_item_t *l_cmd_item = DAP_NEW_Z(dap_chain_node_cmd_item_t);
+    dap_snprintf(l_cmd_item->name,sizeof (l_cmd_item->name),"%s",a_name);
+    l_cmd_item->doc = strdup( a_doc);
+    l_cmd_item->doc_ex = strdup( a_doc_ex);
+    l_cmd_item->func = a_func;
+    l_cmd_item->arg_func = a_arg_func;
+    HASH_ADD_STR(s_commands,name,l_cmd_item);
+    log_it(L_DEBUG,"Added command %s",l_cmd_item->name);
+}
+
+/**
+ * @brief dap_chain_node_cli_command_get_first
+ * @return
+ */
+dap_chain_node_cmd_item_t* dap_chain_node_cli_cmd_get_first()
+{
+    return s_commands;
+}
+
+/**
+ * @brief dap_chain_node_cli_command_find
+ * @param a_name
+ * @return
+ */
+dap_chain_node_cmd_item_t* dap_chain_node_cli_cmd_find(const char *a_name)
+{
+    dap_chain_node_cmd_item_t *l_cmd_item = NULL;
+    HASH_FIND_STR(s_commands,a_name,l_cmd_item);
+    return l_cmd_item;
+}
+
+
+/**
+ * Initialization of the server side of the interaction
+ * with the console kelvin-node-cli
+ *
+ * return 0 if OK, -1 error
+ */
+int dap_chain_node_cli_init(dap_config_t * g_config)
+{
+#ifndef _WIN32
+    struct sockaddr_un l_server_addr={0};
+    l_server_addr.sun_family =  AF_UNIX;
+    snprintf(l_server_addr.sun_path,sizeof(l_server_addr.sun_path), "%s", dap_config_get_item_str( g_config, "conserver", "listen_unix_socket_path") );
+#else
+   pthread_t threadId;
+#endif
+
+    struct sockaddr_in server_addr;
+    SOCKET sockfd = -1;
+
+    bool l_conserver_enabled = dap_config_get_item_bool_default( g_config, "conserver", "enabled", true );
+
+    if ( !l_conserver_enabled ) {
+
+        log_it( L_WARNING, "Console Server is dissabled." );
+        return 0;
+    }
+
+#ifdef __WIN32
+    WSADATA wsaData;
+    int ret = WSAStartup(MAKEWORD(2, 2), &wsaData);
+    if (ret != 0) {
+        log_it(L_CRITICAL, "Couldn't init Winsock DLL, error: %d", ret);
+        return 2;
+    }
+#endif
+
+    dap_chain_node_cli_cmd_item_create("global_db", com_global_db, NULL, "Work with global database",
+            "global_db cells add -cell <cell id> \n"
+            "global_db flush \n\n"
+//                    "global_db wallet_info set -addr <wallet address> -cell <cell id> \n\n"
+            );
+
+    dap_chain_node_cli_cmd_item_create("node", com_node, NULL, "Work with node",
+            "node add  -net <net name> -addr {<node address> | -alias <node alias>} {-port <port>} -cell <cell id>  {-ipv4 <ipv4 external address> | -ipv6 <ipv6 external address>}\n\n"
+                    "node del  -net <net name> -addr <node address> | -alias <node alias>\n\n"
+                    "node link {add|del}  -net <net name> {-addr <node address> | -alias <node alias>} -link <node address>\n\n"
+                    "node alias -addr <node address> -alias <node alias>\n\n"
+                    "node connect {<node address> | -alias <node alias> | auto}\n\n"
+                    "node handshake {<node address> | -alias <node alias>}\n"
+                    "node dump -net <net name> [ -addr <node address> | -alias <node alias>] [-full]\n\n"
+                                        );
+    dap_chain_node_cli_cmd_item_create ("ping", com_ping, NULL, "Send ICMP ECHO_REQUEST to network hosts",
+            "ping [-c <count>] host\n");
+    dap_chain_node_cli_cmd_item_create ("traceroute", com_traceroute, NULL, "Print the hops and time of packets trace to network host",
+            "traceroute host\n");
+    dap_chain_node_cli_cmd_item_create ("tracepath", com_tracepath, NULL, "Traces path to a network host along this path",
+            "tracepath host\n");
+    dap_chain_node_cli_cmd_item_create ("help", com_help, NULL, "Description of command parameters",
+                                        "help [<command>]\n"
+                                        "\tObtain help for <command> or get the total list of the commands\n"
+                                        );
+    dap_chain_node_cli_cmd_item_create ("?", com_help, NULL, "Synonym for \"help\"",
+                                        "? [<command>]\n"
+                                        "\tObtain help for <command> or get the total list of the commands\n"
+                                        );
+    dap_chain_node_cli_cmd_item_create("wallet", com_tx_wallet, NULL, "Wallet operations",
+            "wallet [new -w <wallet_name> [-sign <sign_type>] [-restore <hex value>] [-net <net_name>] [-force]| list | info -addr <addr> -w <wallet_name> -net <net_name>]\n");
+
+    // Token commands
+    dap_chain_node_cli_cmd_item_create ("token_decl_update", com_token_decl_update, NULL, "Token declaration update",
+            "\nPrivate token declaration update\n"
+            "\t token_decl_update -net <net name> -chain <chain name> -token <token ticker> -type private -flags [<Flag 1>][,<Flag 2>]...[,<Flag N>]...  [-<Param name 1> <Param Value 1>] [-Param name 2> <Param Value 2>] ...[-<Param Name N> <Param Value N>]\n"
+            "\t   Update private token <token ticker> for <netname>:<chain name> with flags <Flag 1>,<Flag2>...<Flag N>"
+            "\t   and custom parameters list <Param 1>, <Param 2>...<Param N>."
+            "\n"
+            "==Flags=="
+            "\t ALL_BLOCKED:\t Blocked all permissions, usefull add it first and then add allows what you want to allow\n"
+            "\t ALL_ALLOWED:\t Allowed all permissions if not blocked them. Be careful with this mode\n"
+            "\t ALL_FROZEN:\t All permissions are temprorary frozen\n"
+            "\t ALL_UNFROZEN:\t Unfrozen permissions\n"
+            "\t STATIC_ALL:\t No token manipulations after declarations at all. Token declares staticly and can't variabed after\n"
+            "\t STATIC_FLAGS:\t No token manipulations after declarations with flags\n"
+            "\t STATIC_PERMISSIONS_ALL:\t No all permissions lists manipulations after declarations\n"
+            "\t STATIC_PERMISSIONS_DATUM_TYPE:\t No datum type permissions lists manipulations after declarations\n"
+            "\t STATIC_PERMISSIONS_TX_SENDER:\t No tx sender permissions lists manipulations after declarations\n"
+            "\t STATIC_PERMISSIONS_TX_RECEIVER:\t No tx receiver permissions lists manipulations after declarations\n"
+            "\n"
+            "==Params==\n"
+            "General:\n"
+            "\t -flags_set <value>:\t Set list of flags from <value> to token declaration\n"
+            "\t -flags_unset <value>:\t Unset list of flags from <value> from token declaration\n"
+            "\t -total_supply <value>:\t Set total supply - emission's maximum - to the <value>\n"
+            "\t -signs_valid <value>:\t Set valid signatures count's minimum\n"
+            "\t -signs_add <value>:\t Add signature's pkey fingerprint to the list of owners\n"
+            "\t -signs_remove <value>:\t Remove signature's pkey fingerprint from the owners\n"
+            "\nDatum type allowed/blocked updates:\n"
+            "\t -datum_type_allowed_add <value>:\t Add allowed datum type(s)\n"
+            "\t -datum_type_allowed_remove <value>:\t Remove datum type(s) from allowed\n"
+            "\t -datum_type_allowed_clear:\t Remove all datum types from allowed\n"
+            "\t -datum_type_blocked_add <value>:\t Add blocked datum type(s)\n"
+            "\t -datum_type_blocked_remove <value>:\t Remove datum type(s) from blocked\n"
+            "\t -datum_type_blocked_clear:\t Remove all datum types from blocked\n"
+            "\nTx receiver addresses allowed/blocked updates:\n"
+            "\t -tx_receiver_allowed_add <value>:\t Add allowed tx receiver(s)\n"
+            "\t -tx_receiver_allowed_remove <value>:\t Remove tx receiver(s) from allowed\n"
+            "\t -tx_receiver_allowed_clear:\t Remove all tx receivers from allowed\n"
+            "\t -tx_receiver_blocked_add <value>:\t Add blocked tx receiver(s)\n"
+            "\t -tx_receiver_blocked_remove <value>:\t Remove tx receiver(s) from blocked\n"
+            "\t -tx_receiver_blocked_clear:\t Remove all tx receivers from blocked\n"
+            "\nTx sender addresses allowed/blocked updates:\n"
+            "\t -tx_sender_allowed_add <value>:\t Add allowed tx sender(s)\n"
+            "\t -tx_sender_allowed_remove <value>:\t Remove tx sender(s) from allowed\n"
+            "\t -tx_sender_allowed_clear:\t Remove all tx senders from allowed\n"
+            "\t -tx_sender_blocked_add <value>:\t Add allowed tx sender(s)\n"
+            "\t -tx_sender_blocked_remove <value>:\t Remove tx sender(s) from blocked\n"
+            "\t -tx_sender_blocked_clear:\t Remove all tx sender(s) from blocked\n"
+            "\n"
+            );
+    // Token commands
+    dap_chain_node_cli_cmd_item_create ("token_decl", com_token_decl, NULL, "Token declaration",
+            "Simple token declaration:\n"
+            "\t token_decl -net <net name> -chain <chain name> -token <token ticker> -total_supply <total supply> -signs_total <sign total> -signs_emission <signs for emission> -certs <certs list>\n"
+            "\t\  Declare new simple token for <netname>:<chain name> with ticker <token ticker>, maximum emission <total supply> and <signs for emission> from <signs total> signatures on valid emission\n"
+            "\nExtended private token declaration\n"
+            "\t token_decl -net <net name> -chain <chain name> -token <token ticker> -type private -flags [<Flag 1>][,<Flag 2>]...[,<Flag N>]...  [-<Param name 1> <Param Value 1>] [-Param name 2> <Param Value 2>] ...[-<Param Name N> <Param Value N>]\n"
+            "\t   Declare new token for <netname>:<chain name> with ticker <token ticker>, flags <Flag 1>,<Flag2>...<Flag N>"
+            "\t   and custom parameters list <Param 1>, <Param 2>...<Param N>."
+            "\n"
+            "==Flags=="
+            "\t ALL_BLOCKED:\t Blocked all permissions, usefull add it first and then add allows what you want to allow\n"
+            "\t ALL_ALLOWED:\t Allowed all permissions if not blocked them. Be careful with this mode\n"
+            "\t ALL_FROZEN:\t All permissions are temprorary frozen\n"
+            "\t ALL_UNFROZEN:\t Unfrozen permissions\n"
+            "\t STATIC_ALL:\t No token manipulations after declarations at all. Token declares staticly and can't variabed after\n"
+            "\t STATIC_FLAGS:\t No token manipulations after declarations with flags\n"
+            "\t STATIC_PERMISSIONS_ALL:\t No all permissions lists manipulations after declarations\n"
+            "\t STATIC_PERMISSIONS_DATUM_TYPE:\t No datum type permissions lists manipulations after declarations\n"
+            "\t STATIC_PERMISSIONS_TX_SENDER:\t No tx sender permissions lists manipulations after declarations\n"
+            "\t STATIC_PERMISSIONS_TX_RECEIVER:\t No tx receiver permissions lists manipulations after declarations\n"
+            "\n"
+            "==Params==\n"
+            "General:\n"
+            "\t -flags <value>:\t List of flags from <value> to token declaration\n"
+            "\t -total_supply <value>:\t Set total supply - emission's maximum - to the <value>\n"
+            "\t -signs_valid <value>:\t Set valid signatures count's minimum\n"
+            "\t -signs <value>:\t Signature's fingerprint list\n"
+            "\nDatum type allowed/blocked:\n"
+            "\t -datum_type_allowed <value>:\t Set allowed datum type(s)\n"
+            "\t -datum_type_blocked <value>:\t Set blocked datum type(s)\n"
+            "\nTx receiver addresses allowed/blocked:\n"
+            "\t -tx_receiver_allowed <value>:\t Set allowed tx receiver(s)\n"
+            "\t -tx_receiver_blocked <value>:\t Set blocked tx receiver(s)\n"
+            "\nTx sender addresses allowed/blocked:\n"
+            "\t -tx_sender_allowed <value>:\t Set allowed tx sender(s)\n"
+            "\t -tx_sender_blocked <value>:\t Set allowed tx sender(s)\n"
+            "\n"
+            );
+    /// -------- General tsd types ----
+    // Flags set/unsed
+    #define DAP_CHAIN_DATUM_TOKEN_TSD_TYPE_SET_FLAGS           0x0001
+    #define DAP_CHAIN_DATUM_TOKEN_TSD_TYPE_UNSET_FLAGS         0x0002
+
+    // Total supply limits
+    #define DAP_CHAIN_DATUM_TOKEN_TSD_TYPE_TOTAL_SUPPLY        0x0003
+
+    // Set total signs count value to set to be valid
+    #define DAP_CHAIN_DATUM_TOKEN_TSD_TYPE_TOTAL_SIGNS_VALID   0x0004
+
+    // Add owner signature's pkey fingerprint
+    #define DAP_CHAIN_DATUM_TOKEN_TSD_TYPE_TOTAL_SIGNS_ADD     0x0006
+
+    // Remove owner signature by pkey fingerprint
+    #define DAP_CHAIN_DATUM_TOKEN_TSD_TYPE_TOTAL_SIGNS_REMOVE  0x0007
+
+
+
+    /// ------- Permissions list flags, grouped by update-remove-clear operations --------
+    // Allowed datum types list update, remove or clear
+    #define DAP_CHAIN_DATUM_TOKEN_TSD_TYPE_DATUM_TYPE_ALLOWED_UPDATE       0x0010
+    #define DAP_CHAIN_DATUM_TOKEN_TSD_TYPE_DATUM_TYPE_ALLOWED_REMOVE       0x0011
+    #define DAP_CHAIN_DATUM_TOKEN_TSD_TYPE_DATUM_TYPE_ALLOWED_CLEAR        0x0012
+
+    // Blocked datum types list update, remove or clear
+    #define DAP_CHAIN_DATUM_TOKEN_TSD_TYPE_DATUM_TYPE_BLOCKED_UPDATE       0x0013
+    #define DAP_CHAIN_DATUM_TOKEN_TSD_TYPE_DATUM_TYPE_BLOCKED_REMOVE       0x0014
+    #define DAP_CHAIN_DATUM_TOKEN_TSD_TYPE_DATUM_TYPE_BLOCKED_CLEAR        0x0015
+
+    //Allowed tx receiver addres list update, remove or clear
+    #define DAP_CHAIN_DATUM_TOKEN_TSD_TYPE_TX_RECEIVER_ALLOWED_UPDATE       0x0014
+    #define DAP_CHAIN_DATUM_TOKEN_TSD_TYPE_TX_RECEIVER_ALLOWED_REMOVE       0x0015
+    #define DAP_CHAIN_DATUM_TOKEN_TSD_TYPE_TX_RECEIVER_ALLOWED_CLEAR        0x0016
+
+    //Blocked tx receiver addres list update, remove or clear
+    #define DAP_CHAIN_DATUM_TOKEN_TSD_TYPE_TX_RECEIVER_BLOCKED_UPDATE       0x0017
+    #define DAP_CHAIN_DATUM_TOKEN_TSD_TYPE_TX_RECEIVER_BLOCKED_REMOVE       0x0018
+    #define DAP_CHAIN_DATUM_TOKEN_TSD_TYPE_TX_RECEIVER_BLOCKED_CLEAR        0x0019
+
+
+    //Allowed tx sender addres list update, remove or clear
+    #define DAP_CHAIN_DATUM_TOKEN_TSD_TYPE_TX_SENDER_ALLOWED_UPDATE       0x0020
+    #define DAP_CHAIN_DATUM_TOKEN_TSD_TYPE_TX_SENDER_ALLOWED_REMOVE       0x0021
+    #define DAP_CHAIN_DATUM_TOKEN_TSD_TYPE_TX_SENDER_ALLOWED_CLEAR        0x0022
+
+    //Blocked tx sender addres list update, remove or clear
+    #define DAP_CHAIN_DATUM_TOKEN_TSD_TYPE_TX_SENDER_BLOCKED_UPDATE       0x0023
+    #define DAP_CHAIN_DATUM_TOKEN_TSD_TYPE_TX_SENDER_BLOCKED_REMOVE       0x0024
+    #define DAP_CHAIN_DATUM_TOKEN_TSD_TYPE_TX_SENDER_BLOCKED_CLEAR        0x0025
+
+
+    dap_chain_node_cli_cmd_item_create ("token_decl_sign", com_token_decl_sign, NULL, "Token declaration add sign",
+            "token_decl_sign -net <net name> -chain <chain name> -datum <datum_hash> -certs <certs list>\n"
+            "\t Sign existent <datum hash> in mempool with <certs list>\n"
+            );
+
+    dap_chain_node_cli_cmd_item_create ("token_emit", com_token_emit, NULL, "Token emission",
+            "token_emit -net <net name> -chain_emission <chain for emission> -chain_base_tx <chain for base tx> -addr <addr> -token <token ticker> -certs <cert> -emission_value <val>\n");
+
+    dap_chain_node_cli_cmd_item_create ("mempool_list", com_mempool_list, NULL, "List mempool entries for selected chain network and chain id",
+            "mempool_list -net <net name> -chain <chain name>\n");
+
+    dap_chain_node_cli_cmd_item_create ("mempool_proc", com_mempool_proc, NULL, "Proc mempool entries for selected chain network and chain id",
+            "mempool_proc -net <net name> -chain <chain name>\n");
+
+    dap_chain_node_cli_cmd_item_create ("mempool_delete", com_mempool_delete, NULL, "Delete datum with hash <datum hash>",
+            "mempool_delete -net <net name> -chain <chain name> -datum <datum hash>\n");
+
+    dap_chain_node_cli_cmd_item_create ("mempool_add_ca", com_mempool_add_ca, NULL,
+                                        "Add pubic certificate into the mempool to prepare its way to chains",
+            "mempool_add_ca -net <net name> [-chain <chain name>] -ca_name <Certificate name>\n");
+
+
+    // Transaction commands
+    dap_chain_node_cli_cmd_item_create ("tx_create", com_tx_create, NULL, "Make transaction",
+            "tx_create -net <net name> -chain <chain name> -from_wallet <name> -to_addr <addr> -token <token ticker> -value <value> [-fee <addr> -value_fee <val>]\n" );
+    dap_chain_node_cli_cmd_item_create ("tx_cond_create", com_tx_cond_create, NULL, "Make cond transaction",
+            "tx_cond_create todo\n" );
+    dap_chain_node_cli_cmd_item_create ("tx_verify", com_tx_verify, NULL, "Verifing transaction",
+            "tx_verify  -wallet <wallet name> \n" );
+
+    // Transaction history
+    dap_chain_node_cli_cmd_item_create("tx_history", com_tx_history, NULL, "Transaction history (for address or by hash)",
+            "tx_history  [-addr <addr> | -w <wallet name> | -tx <tx_hash>] -net <net name> -chain <chain name>\n");
+
+    // vpn client
+    dap_chain_node_cli_cmd_item_create ("vpn_client", com_vpn_client, NULL, "VPN client control",
+    "vpn_client [start -addr <server address> -port <server port>| stop | status]\n");
+
+
+    // Log
+    dap_chain_node_cli_cmd_item_create ("print_log", com_print_log, NULL, "Print log info",
+                "print_log [ts_after <timestamp >] [limit <line numbers>]\n" );
+
+    // Statisticss
+    dap_chain_node_cli_cmd_item_create("stats", com_stats, NULL, "Print statistics",
+                "stats cpu");
+
+    // Exit
+    dap_chain_node_cli_cmd_item_create ("exit", com_exit, NULL, "Stop application and exit",
+                "exit\n" );
+
+    // create thread for waiting of clients
+    pthread_t l_thread_id;
+
+    l_listen_port = dap_config_get_item_uint16_default( g_config, "conserver", "listen_port_tcp",0);
+
+    const char * l_listen_unix_socket_path = dap_config_get_item_str( g_config, "conserver", "listen_unix_socket_path");
+    const char * l_listen_unix_socket_permissions_str = dap_config_get_item_str( g_config, "conserver", "listen_unix_socket_permissions");
+    mode_t l_listen_unix_socket_permissions = 0770;
+
+    if ( l_listen_unix_socket_path && l_listen_unix_socket_permissions ) {
+        if ( l_listen_unix_socket_permissions_str ) {
+            dap_sscanf(l_listen_unix_socket_permissions_str,"%ou", &l_listen_unix_socket_permissions );
+        }
+        log_it( L_INFO, "Console interace on path %s (%04o) ", l_listen_unix_socket_path, l_listen_unix_socket_permissions );
+
+      #ifndef _WIN32
+
+        if ( server_sockfd >= 0 ) {
+            dap_chain_node_cli_delete( );
+            server_sockfd = 0;
+        }
+
+        // create socket
+        sockfd = socket( AF_UNIX, SOCK_STREAM, 0 );
+        if( sockfd == INVALID_SOCKET )
+            return -1;
+
+        //int gdsg = sizeof(struct sockaddr_un);
+
+        if ( access( l_listen_unix_socket_path , R_OK) != -1 )
+            unlink( l_listen_unix_socket_path );
+
+        // connecting the address with a socket
+        if( bind(sockfd, (const struct sockaddr*) &l_server_addr, sizeof(struct sockaddr_un)) == SOCKET_ERROR) {
+            // errno = EACCES  13  Permission denied
+            if ( errno == EACCES ) // EACCES=13
+                log_it( L_ERROR, "Server can't start(err=%d). Can't create file=%s [Permission denied]", errno,
+                        l_listen_unix_socket_path );
+            else
+                log_it( L_ERROR, "Server can't start(err=%d). May be problem with file=%s?", errno, l_listen_unix_socket_path );
+            closesocket( sockfd );
+            return -2;
+        }
+        chmod(l_listen_unix_socket_path,l_listen_unix_socket_permissions);
+
+      #else
+
+//    Sleep( 3000 );
+
+        if( pthread_create(&threadId, NULL, thread_pipe_func, (void*) (intptr_t) sockfd) != 0 ) {
+            closesocket( sockfd );
+            return -7;
+        }
+
+        return 0;
+      #endif
+
+    }
+    else if (l_listen_port ){
+
+        const char *l_listen_addr_str = dap_config_get_item_str(g_config, "conserver", "listen_address");
+
+        log_it( L_INFO, "Console interace on addr %s port %u ", l_listen_addr_str, l_listen_port );
+
+        server_addr.sin_family = AF_INET;
+#ifdef _WIN32
+        struct in_addr _in_addr = { { .S_addr = htonl(INADDR_LOOPBACK) } };
+        server_addr.sin_addr = _in_addr;
+        server_addr.sin_port = l_listen_port;
+#else
+        inet_pton( AF_INET, l_listen_addr_str, &server_addr.sin_addr );
+        server_addr.sin_port = htons( (uint16_t)l_listen_port );
+#endif
+        // create socket
+        if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET ) {
+#ifdef __WIN32
+            _set_errno(WSAGetLastError());
+#endif
+            log_it( L_ERROR, "Console Server: can't create socket, err %d", errno );
+            return -3;
+        }
+
+        // connecting the address with a socket
+        if ( bind(sockfd, (struct sockaddr *) &server_addr, sizeof(server_addr)) == SOCKET_ERROR ) {
+#ifdef __WIN32
+            _set_errno(WSAGetLastError());
+#endif
+            log_it( L_ERROR, "Console Server: can't bind socket, err %d", errno );
+            closesocket( sockfd );
+            return -4;
+        }
+    }else {
+        log_it (L_INFO, "Not defined console interface");
+        return 0;
+    }
+
+    // turn on reception of connections
+    if( listen(sockfd, MAX_CONSOLE_CLIENTS) == SOCKET_ERROR )
+        return -5;
+
+    if( pthread_create(&l_thread_id, NULL, thread_main_func, (void*) (intptr_t) sockfd) != 0 ) {
+        closesocket( sockfd );
+        return -6;
+    }
+
+    // in order to thread not remain in state "dead" after completion
+    pthread_detach( l_thread_id );
+    server_sockfd = sockfd;
+
+    return 0;
+}
+
+/**
+ * Deinitialization of the server side
+ *
+ */
+void dap_chain_node_cli_delete(void)
+{
+    if(server_sockfd >= 0)
+        closesocket(server_sockfd);
+#ifdef __WIN32
+    WSACleanup();
+#endif
+    // deinit client for handshake
+    dap_chain_node_client_deinit();
+}
diff --git a/libdap-chain-net/dap_chain_node_cli.h b/libdap-chain-net/dap_chain_node_cli.h
new file mode 100644
index 0000000000000000000000000000000000000000..e9df748e7ebb56c7c81a2436e018ef33c4c14a18
--- /dev/null
+++ b/libdap-chain-net/dap_chain_node_cli.h
@@ -0,0 +1,78 @@
+/*
+ * Authors:
+ * Dmitriy A. Gearasimov <gerasimov.dmitriy@demlabs.net>
+ * Alexander Lysikov <alexander.lysikov@demlabs.net>
+ * DeM Labs Inc.   https://demlabs.net
+ * Kelvin Project https://github.com/kelvinblockchain
+ * Copyright  (c) 2019
+ * All rights reserved.
+
+ This file is part of DAP (Deus Applications Prototypes) the open source project
+
+ DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ DAP is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with any DAP based project.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "dap_common.h"
+#include "dap_config.h"
+#include "uthash.h"
+
+#ifndef _WIN32
+#include <unistd.h> // for close
+#define closesocket close
+typedef int SOCKET;
+#define SOCKET_ERROR    -1  // for win32 =  (-1)
+#define INVALID_SOCKET  -1  // for win32 =  (SOCKET)(~0)
+#endif
+
+
+typedef int cmdfunc_t(int argc, char ** argv, void *arg_func, char **str_reply);
+
+typedef struct dap_chain_node_cmd_item{
+    char name[32]; /* User printable name of the function. */
+    cmdfunc_t *func; /* Function to call to do the job. */
+    void *arg_func; /* additional argument of function*/
+    char *doc; /* Documentation for this function.  */
+    char *doc_ex; /* Full documentation for this function.  */
+    UT_hash_handle hh;
+} dap_chain_node_cmd_item_t;
+
+
+// Read from socket
+long s_recv(SOCKET sock, unsigned char *buf, size_t bufsize, int timeout);
+
+/**
+ *  Look up NAME as the name of a command, and return a pointer to that
+ *  command.  Return a NULL pointer if NAME isn't a command name.
+ */
+dap_chain_node_cmd_item_t* dap_chain_node_cli_cmd_get_first();
+dap_chain_node_cmd_item_t* dap_chain_node_cli_cmd_find(const char *a_name);
+void dap_chain_node_cli_cmd_item_create(const char * a_name, cmdfunc_t *a_func, void *a_arg_func, const char *a_doc, const char *a_doc_ex);
+
+void dap_chain_node_cli_set_reply_text(char **str_reply, const char *str, ...);
+
+int dap_chain_node_cli_find_option_val( char** argv, int arg_start, int arg_end, const char *opt_name, const char **opt_value);
+
+
+/**
+ * Initialization of the server side of the interaction
+ * with the console kelvin-node-cli
+ */
+int dap_chain_node_cli_init(dap_config_t * g_config);
+
+/**
+ * Deinitialization of the server side
+ */
+void dap_chain_node_cli_delete(void);
diff --git a/libdap-chain-net/dap_chain_node_cli_cmd.c b/libdap-chain-net/dap_chain_node_cli_cmd.c
new file mode 100644
index 0000000000000000000000000000000000000000..1f15bd943d939d884a44935711ef1ac8ff4c48b0
--- /dev/null
+++ b/libdap-chain-net/dap_chain_node_cli_cmd.c
@@ -0,0 +1,3488 @@
+/*
+ * Authors:
+ * Dmitriy A. Gerasimov <gerasimov.dmitriy@demlabs.net>
+ * Alexander Lysikov <alexander.lysikov@demlabs.net>
+ * DeM Labs Inc.   https://demlabs.net
+ * Kelvin Project https://github.com/kelvinblockchain
+ * Copyright  (c) 2019
+ * All rights reserved.
+
+ This file is part of DAP (Deus Applications Prototypes) the open source project
+
+ DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ DAP is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with any DAP based project.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <assert.h>
+#include <ctype.h>
+#include <dirent.h>
+
+#ifdef WIN32
+#include <winsock2.h>
+#include <windows.h>
+#include <mswsock.h>
+#include <ws2tcpip.h>
+#include <io.h>
+#include <wepoll.h>
+#else
+#include <sys/types.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <signal.h>
+#endif
+#include <pthread.h>
+
+#include "iputils/iputils.h"
+
+#include "uthash.h"
+#include "utlist.h"
+
+#include "dap_string.h"
+#include "dap_hash.h"
+#include "dap_chain_common.h"
+#include "dap_strfuncs.h"
+#include "dap_list.h"
+#include "dap_string.h"
+#include "dap_cert.h"
+#include "dap_cert_file.h"
+#include "dap_chain_wallet.h"
+#include "dap_chain_node.h"
+#include "dap_chain_global_db.h"
+#include "dap_chain_node_client.h"
+#include "dap_chain_node_remote.h"
+#include "dap_chain_node_cli_cmd.h"
+#include "dap_chain_node_cli_cmd_tx.h"
+#include "dap_chain_node_ping.h"
+#include "dap_chain_net_srv.h"
+#include "dap_chain_cell.h"
+
+#include "dap_chain_common.h"
+#include "dap_chain_datum.h"
+#include "dap_chain_datum_token.h"
+#include "dap_chain_datum_tx_items.h"
+#include "dap_chain_ledger.h"
+#include "dap_chain_mempool.h"
+#include "dap_chain_global_db.h"
+#include "dap_chain_global_db_remote.h"
+//#include "dap_chain_gdb.h"
+
+#include "dap_stream_ch_chain_net.h"
+#include "dap_stream_ch_chain.h"
+#include "dap_stream_ch_chain_pkt.h"
+#include "dap_stream_ch_chain_net_pkt.h"
+
+#define LOG_TAG "chain_node_cli_cmd"
+
+/**
+ * Find in base addr by alias
+ *
+ * return addr, NULL if not found
+ */
+dap_chain_node_addr_t* dap_chain_node_addr_get_by_alias(dap_chain_net_t * a_net, const char *a_alias)
+{
+    dap_chain_node_addr_t *l_addr = NULL;
+    if(!a_alias)
+        return NULL;
+    const char *a_key = a_alias;
+    size_t l_addr_size = 0;
+    l_addr = (dap_chain_node_addr_t*) (void*) dap_chain_global_db_gr_get(a_key, &l_addr_size,
+            a_net->pub.gdb_nodes_aliases);
+    if(l_addr_size != sizeof(dap_chain_node_addr_t)) {
+//        l_addr = DAP_NEW_Z(dap_chain_node_addr_t);
+//        if(hex2bin((char*) l_addr, (const unsigned char *) addr_str, sizeof(dap_chain_node_addr_t) * 2) == -1) {
+        DAP_DELETE(l_addr);
+//            l_addr = NULL;
+//        }
+    }
+//    DAP_DELETE(addr_str);
+    return l_addr;
+}
+
+/**
+ * Find in base alias by addr
+ *
+ * return list of addr, NULL if not found
+ */
+static dap_list_t* get_aliases_by_name(dap_chain_net_t * l_net, dap_chain_node_addr_t *a_addr)
+{
+    if(!a_addr)
+        return NULL;
+    dap_list_t *list_aliases = NULL;
+    size_t data_size = 0;
+    // read all aliases
+    dap_global_db_obj_t *objs = dap_chain_global_db_gr_load(l_net->pub.gdb_nodes_aliases, &data_size);
+    if(!objs || !data_size)
+        return NULL;
+    for(size_t i = 0; i < data_size; i++) {
+        //dap_chain_node_addr_t addr_i;
+        dap_global_db_obj_t *obj = objs + i;
+        if(!obj)
+            break;
+        dap_chain_node_addr_t *l_addr = (dap_chain_node_addr_t*) (void*) obj->value;
+        if(l_addr && obj->value_len == sizeof(dap_chain_node_addr_t) && a_addr->uint64 == l_addr->uint64) {
+            list_aliases = dap_list_prepend(list_aliases, strdup(obj->key));
+        }
+    }
+    dap_chain_global_db_objs_delete(objs, data_size);
+    return list_aliases;
+}
+
+static dap_chain_node_addr_t* s_node_info_get_addr(dap_chain_net_t * a_net, dap_chain_node_info_t *a_node_info,
+        dap_chain_node_addr_t *a_addr, const char *a_alias_str)
+{
+    dap_chain_node_addr_t *l_address = NULL;
+    if(a_alias_str && !a_addr->uint64) {
+        l_address = dap_chain_node_addr_get_by_alias(a_net, a_alias_str);
+    }
+    if(a_addr->uint64) {
+        l_address = DAP_NEW(dap_chain_node_addr_t);
+        l_address->uint64 = a_addr->uint64;
+    }
+    return l_address;
+}
+
+/**
+ * Read node from base
+ */
+static dap_chain_node_info_t* node_info_read_and_reply(dap_chain_net_t * a_net, dap_chain_node_addr_t *a_address,
+        char **a_str_reply)
+{
+    char *l_key = dap_chain_node_addr_to_hash_str(a_address);
+    if(!l_key)
+    {
+        dap_chain_node_cli_set_reply_text(a_str_reply, "can't calculate hash of addr");
+        return NULL;
+    }
+    size_t node_info_size = 0;
+    dap_chain_node_info_t *node_info;
+    // read node
+    node_info = (dap_chain_node_info_t *) dap_chain_global_db_gr_get(l_key, &node_info_size, a_net->pub.gdb_nodes);
+
+    if(!node_info) {
+        dap_chain_node_cli_set_reply_text(a_str_reply, "node not found in base");
+        DAP_DELETE(l_key);
+        return NULL;
+    }
+    if(!node_info->hdr.ext_port)
+        node_info->hdr.ext_port = 8079;
+    size_t node_info_size_must_be = dap_chain_node_info_get_size(node_info);
+    if(node_info_size_must_be != node_info_size) {
+        dap_chain_node_cli_set_reply_text(a_str_reply, "node has bad size in base=%u (must be %u)", node_info_size,
+                node_info_size_must_be);
+        DAP_DELETE(node_info);
+        DAP_DELETE(l_key);
+        return NULL;
+    }
+
+//    dap_chain_node_info_t *node_info = dap_chain_node_info_deserialize(str, (str) ? strlen(str) : 0);
+//    if(!node_info) {
+//        set_reply_text(str_reply, "node has invalid format in base");
+//    }
+//    DAP_DELETE(str);
+    DAP_DELETE(l_key);
+    return node_info;
+}
+
+/**
+ * Save node to base
+ */
+static bool node_info_save_and_reply(dap_chain_net_t * a_net, dap_chain_node_info_t *a_node_info, char **str_reply)
+{
+    if(!a_node_info || !a_node_info->hdr.address.uint64) {
+        dap_chain_node_cli_set_reply_text(str_reply, "node addr not found");
+        return false;
+    }
+    char *a_key = dap_chain_node_addr_to_hash_str(&a_node_info->hdr.address);
+    if(!a_key)
+    {
+        dap_chain_node_cli_set_reply_text(str_reply, "can't calculate hash for addr");
+        return NULL;
+    }
+    //char *a_value = dap_chain_node_info_serialize(node_info, NULL);
+    size_t l_node_info_size = dap_chain_node_info_get_size(a_node_info);
+    //dap_chain_node_info_t *l_node_info = DAP_NEW_Z_SIZE(dap_chain_node_info_t, l_node_info_size);
+    //memcpy(l_node_info, a_node_info, l_node_info_size );
+
+    //size_t data_len_out = 0;
+    //dap_chain_node_info_t *a_node_info1 = dap_chain_global_db_gr_get(a_key, &data_len_out, a_net->pub.gdb_nodes);
+
+    bool res = dap_chain_global_db_gr_set(a_key, (uint8_t *) a_node_info, l_node_info_size, a_net->pub.gdb_nodes);
+
+    //data_len_out = 0;
+    //dap_chain_node_info_t *a_node_info2 = dap_chain_global_db_gr_get(a_key, &data_len_out, a_net->pub.gdb_nodes);
+    //DAP_DELETE(a_key);
+    //DAP_DELETE(a_value);
+    return res;
+}
+
+/**
+ * Handler of command 'global_db node add'
+ *
+ * str_reply[out] for reply
+ * return 0 Ok, -1 error
+ */
+static int node_info_add_with_reply(dap_chain_net_t * a_net, dap_chain_node_info_t *a_node_info,
+        const char *a_alias_str,
+        const char *a_cell_str, const char *a_ipv4_str, const char *a_ipv6_str, char **a_str_reply)
+{
+
+    if(!a_node_info->hdr.address.uint64) {
+        dap_chain_node_cli_set_reply_text(a_str_reply, "not found -addr parameter");
+        return -1;
+    }
+    if(!a_cell_str) {
+        dap_chain_node_cli_set_reply_text(a_str_reply, "not found -cell parameter");
+        return -1;
+    }
+    if(a_ipv4_str)
+        inet_pton(AF_INET, a_ipv4_str, &(a_node_info->hdr.ext_addr_v4));
+    if(a_ipv6_str)
+        inet_pton(AF_INET6, a_ipv6_str, &(a_node_info->hdr.ext_addr_v6));
+
+    // check match addr to cell or no
+    /*dap_chain_node_addr_t *addr = dap_chain_node_gen_addr(&node_info->hdr.cell_id);
+     if(!dap_chain_node_check_addr(&node_info->hdr.address, &node_info->hdr.cell_id)) {
+     set_reply_text(str_reply, "cell does not match addr");
+     return -1;
+     }*/
+    if(a_alias_str) {
+        // add alias
+        if(!dap_chain_node_alias_register(a_net, a_alias_str, &a_node_info->hdr.address)) {
+            log_it(L_WARNING, "can't save alias %s", a_alias_str);
+            dap_chain_node_cli_set_reply_text(a_str_reply, "alias '%s' can't be mapped to addr=0x%lld",
+                    a_alias_str, a_node_info->hdr.address.uint64);
+            return -1;
+        }
+    }
+
+    // write to base
+    bool res = node_info_save_and_reply(a_net, a_node_info, a_str_reply);
+    if(res)
+        dap_chain_node_cli_set_reply_text(a_str_reply, "node added");
+    else
+        return -1;
+    if(res)
+        return 0;
+    return -1;
+}
+
+/**
+ * Handler of command 'global_db node add'
+ *
+ * str_reply[out] for reply
+ * return 0 Ok, -1 error
+ */
+static int node_info_del_with_reply(dap_chain_net_t * a_net, dap_chain_node_info_t *a_node_info, const char *alias_str,
+        char **str_reply)
+{
+    if(!a_node_info->hdr.address.uint64 && !alias_str) {
+        dap_chain_node_cli_set_reply_text(str_reply, "addr not found");
+        return -1;
+    }
+    // check, current node have this addr or no
+    uint64_t l_cur_addr = dap_db_get_cur_node_addr(a_net->pub.name);
+    if(l_cur_addr && l_cur_addr == a_node_info->hdr.address.uint64) {
+        dap_chain_node_cli_set_reply_text(str_reply, "current node cannot be deleted");
+        return -1;
+    }
+
+    // find addr by alias or addr_str
+    dap_chain_node_addr_t *address = s_node_info_get_addr(a_net, a_node_info, &a_node_info->hdr.address, alias_str);
+    if(!address) {
+        dap_chain_node_cli_set_reply_text(str_reply, "alias not found");
+        return -1;
+    }
+    char *a_key = dap_chain_node_addr_to_hash_str(address);
+    if(a_key)
+    {
+        // delete node
+        bool res = dap_chain_global_db_gr_del(dap_strdup(a_key), a_net->pub.gdb_nodes);
+        if(res) {
+            // delete all aliases for node address
+            {
+                dap_list_t *list_aliases = get_aliases_by_name(a_net, address);
+                dap_list_t *list = list_aliases;
+                while(list)
+                {
+                    const char *alias = (const char *) list->data;
+                    dap_chain_node_alias_delete(a_net, alias);
+                    list = dap_list_next(list);
+                }
+                dap_list_free_full(list_aliases, (dap_callback_destroyed_t) free);
+            }
+            // set text response
+            dap_chain_node_cli_set_reply_text(str_reply, "node deleted");
+        }
+        else
+            dap_chain_node_cli_set_reply_text(str_reply, "node not deleted");
+        DAP_DELETE(a_key);
+        DAP_DELETE(address);
+        if(res)
+            return 0;
+        return -1;
+    }
+    dap_chain_node_cli_set_reply_text(str_reply, "addr to delete can't be defined");
+    DAP_DELETE(address);
+    return -1;
+}
+
+/**
+ * Handler of command 'global_db node link'
+ *
+ * cmd 'add' or 'del'
+ * str_reply[out] for reply
+ * return 0 Ok, -1 error
+ */
+static int link_add_or_del_with_reply(dap_chain_net_t * a_net, dap_chain_node_info_t *a_node_info, const char *cmd,
+        const char *a_alias_str,
+        dap_chain_node_addr_t *link, char **a_str_reply)
+{
+    if(!a_node_info->hdr.address.uint64 && !a_alias_str) {
+        dap_chain_node_cli_set_reply_text(a_str_reply, "addr not found");
+        return -1;
+    }
+    if(!link->uint64) {
+        dap_chain_node_cli_set_reply_text(a_str_reply, "link not found");
+        return -1;
+    }
+    // TODO check the presence of link in the node base
+#ifdef DAP_CHAIN_NODE_CHECK_PRESENSE
+        dap_chain_node_cli_set_reply_text(a_str_reply, "node 0x%016llx not found in base", link->uint64);
+        return -1;
+#endif
+
+    // find addr by alias or addr_str
+    dap_chain_node_addr_t *l_address = s_node_info_get_addr(a_net, a_node_info, &a_node_info->hdr.address, a_alias_str);
+    if(!l_address) {
+        dap_chain_node_cli_set_reply_text(a_str_reply, "alias not found");
+        return -1;
+    }
+
+    dap_chain_node_info_t * l_node_info_read = node_info_read_and_reply(a_net, l_address, a_str_reply);
+    size_t l_node_info_read_size = dap_chain_node_info_get_size(l_node_info_read);
+    if(!l_node_info_read)
+        return -1;
+
+    int cmd_int = 0;
+    if(!strcmp(cmd, "add"))
+        cmd_int = 1;
+    else if(!strcmp(cmd, "del"))
+        cmd_int = 2;
+
+    // find link in node_info_read
+    int index_link = -1;
+    for(size_t i = 0; i < l_node_info_read->hdr.links_number; i++) {
+        if(l_node_info_read->links[i].uint64 == link->uint64) {
+            // link already present
+            index_link = (int) i;
+            break;
+        }
+    }
+    bool res_successful = false; // is successful whether add/del
+    // add link
+    if(cmd_int == 1) {
+        if(index_link == -1) {
+            l_node_info_read->hdr.links_number++;
+            l_node_info_read_size = dap_chain_node_info_get_size(l_node_info_read);
+            l_node_info_read = DAP_REALLOC(l_node_info_read, l_node_info_read_size);
+            memcpy(&(l_node_info_read->links[l_node_info_read->hdr.links_number-1]), link, sizeof(dap_chain_node_addr_t));
+            res_successful = true;
+        }
+    }
+    // delete link
+    else if(cmd_int == 2) {
+        // move link list to one item prev
+        if(index_link >= 0) {
+            for(unsigned int j = (unsigned int) index_link; j < (l_node_info_read->hdr.links_number - 1); j++) {
+                memcpy(&(l_node_info_read->links[j]), &(l_node_info_read->links[j + 1]), sizeof(dap_chain_node_addr_t));
+            }
+            l_node_info_read->hdr.links_number--;
+            res_successful = true;
+            l_node_info_read = DAP_REALLOC(l_node_info_read, l_node_info_read_size -= sizeof(*link));
+        }
+    }
+    // save edited node_info
+    if(res_successful) {
+        bool res = node_info_save_and_reply(a_net, l_node_info_read, a_str_reply);
+        if(res) {
+            res_successful = true;
+            if(cmd_int == 1)
+                dap_chain_node_cli_set_reply_text(a_str_reply, "link added");
+            if(cmd_int == 2)
+                dap_chain_node_cli_set_reply_text(a_str_reply, "link deleted");
+        }
+        else {
+            res_successful = false;
+        }
+    }
+    else {
+        if(cmd_int == 1) {
+            if(index_link >= 0)
+                dap_chain_node_cli_set_reply_text(a_str_reply, "link not added because it is already present");
+            else
+                dap_chain_node_cli_set_reply_text(a_str_reply, "link not added");
+        }
+        if(cmd_int == 2) {
+            if(index_link == -1)
+                dap_chain_node_cli_set_reply_text(a_str_reply, "link not deleted because not found");
+            else
+                dap_chain_node_cli_set_reply_text(a_str_reply, "link not deleted");
+        }
+    }
+
+    DAP_DELETE(l_address);
+    DAP_DELETE(l_node_info_read);
+    if(res_successful)
+        return 0;
+    return -1;
+}
+
+/**
+ * Handler of command 'node dump'
+ *
+ * str_reply[out] for reply
+ * return 0 Ok, -1 error
+ */
+static int node_info_dump_with_reply(dap_chain_net_t * a_net, dap_chain_node_addr_t * a_addr, bool a_is_full,
+        const char *a_alias, char **a_str_reply)
+{
+    int l_ret = 0;
+    dap_string_t *l_string_reply = dap_string_new("Node dump:");
+
+    if((a_addr && a_addr->uint64) || a_alias) {
+        dap_chain_node_addr_t *l_addr = NULL;
+        if(a_addr && a_addr->uint64) {
+            l_addr = DAP_NEW(dap_chain_node_addr_t);
+            l_addr->uint64 = a_addr->uint64;
+        } else if(a_alias) {
+            l_addr = dap_chain_node_alias_find(a_net, a_alias);
+        }
+        if(!l_addr) {
+            dap_chain_node_cli_set_reply_text(a_str_reply, "addr not valid");
+            dap_string_free(l_string_reply, true);
+            return -1;
+        }
+        // read node
+        dap_chain_node_info_t *node_info_read = node_info_read_and_reply(a_net, l_addr, a_str_reply);
+        if(!node_info_read) {
+            DAP_DELETE(l_addr);
+            dap_string_free(l_string_reply, true);
+            return -2;
+        }
+
+        // get aliases in form of string
+        dap_string_t *aliases_string = dap_string_new(NULL);
+        dap_list_t *list_aliases = get_aliases_by_name(a_net, l_addr);
+        if(list_aliases)
+        {
+            dap_list_t *list = list_aliases;
+            while(list)
+            {
+                const char *alias = (const char *) list->data;
+                dap_string_append_printf(aliases_string, "\nalias %s", alias);
+                list = dap_list_next(list);
+            }
+            dap_list_free_full(list_aliases, (dap_callback_destroyed_t) free);
+        }
+        else
+            dap_string_append(aliases_string, "\nno aliases");
+
+        const int hostlen = 128;
+        char *host4 = (char*) alloca(hostlen);
+        char *host6 = (char*) alloca(hostlen);
+        struct sockaddr_in sa4 = { .sin_family = AF_INET, .sin_addr = node_info_read->hdr.ext_addr_v4 };
+        const char* str_ip4 = inet_ntop(AF_INET, &(((struct sockaddr_in *) &sa4)->sin_addr), host4, hostlen);
+
+        struct sockaddr_in6 sa6 = { .sin6_family = AF_INET6, .sin6_addr = node_info_read->hdr.ext_addr_v6 };
+        const char* str_ip6 = inet_ntop(AF_INET6, &(((struct sockaddr_in6 *) &sa6)->sin6_addr), host6, hostlen);
+
+        // get links in form of string
+        dap_string_t *links_string = dap_string_new(NULL);
+        for(unsigned int i = 0; i < node_info_read->hdr.links_number; i++) {
+            dap_chain_node_addr_t link_addr = node_info_read->links[i];
+            dap_string_append_printf(links_string, "\nlink%02d address : " NODE_ADDR_FP_STR, i,
+                    NODE_ADDR_FP_ARGS_S(link_addr));
+        }
+
+        dap_string_append_printf(l_string_reply, "\n");
+        char l_port_str[10];
+        sprintf(l_port_str,"%d",node_info_read->hdr.ext_port);
+
+        // set short reply with node param
+        if(!a_is_full)
+            dap_string_append_printf(l_string_reply,
+                    "node address "NODE_ADDR_FP_STR"\tcell 0x%016llx\tipv4 %s\tport: %s\tnumber of links %u",
+                    NODE_ADDR_FP_ARGS_S(node_info_read->hdr.address),
+                    node_info_read->hdr.cell_id.uint64, str_ip4,
+                    node_info_read->hdr.ext_port ? l_port_str : "default",
+                    node_info_read->hdr.links_number);
+        else
+            // set full reply with node param
+            dap_string_append_printf(l_string_reply,
+                    "node address " NODE_ADDR_FP_STR "\ncell 0x%016llx\nipv4 %s\nipv6 %s\nport: %s%s\nlinks %u%s",
+                    NODE_ADDR_FP_ARGS_S(node_info_read->hdr.address),
+                    node_info_read->hdr.cell_id.uint64,
+                    str_ip4, str_ip6,
+                    node_info_read->hdr.ext_port ? l_port_str : "default",
+                    aliases_string->str,
+                    node_info_read->hdr.links_number, links_string->str);
+        dap_string_free(aliases_string, true);
+        dap_string_free(links_string, true);
+
+        DAP_DELETE(l_addr);
+        DAP_DELETE(node_info_read);
+
+    } else { // Dump list
+        dap_global_db_obj_t *l_objs = NULL;
+        size_t l_nodes_count = 0;
+        dap_string_append(l_string_reply, "\n");
+        // read all node
+        l_objs = dap_chain_global_db_gr_load(a_net->pub.gdb_nodes, &l_nodes_count);
+
+        if(!l_nodes_count || !l_objs) {
+            dap_string_append_printf(l_string_reply, "No records\n");
+            dap_chain_node_cli_set_reply_text(a_str_reply, l_string_reply->str);
+            dap_string_free(l_string_reply, true);
+            dap_chain_global_db_objs_delete(l_objs, l_nodes_count);
+            return -1;
+        } else {
+            size_t l_nodes_count_real = 0;
+            dap_string_append_printf(l_string_reply, "Got %u records:\n", l_nodes_count);
+            for(size_t i = 0; i < l_nodes_count; i++) {
+                dap_chain_node_info_t *l_node_info = (dap_chain_node_info_t *) l_objs[i].value;
+                // find addr by alias or addr_str
+                dap_chain_node_addr_t *address = s_node_info_get_addr(a_net, l_node_info, &l_node_info->hdr.address, a_alias);
+                if(!address) {
+                    dap_chain_node_cli_set_reply_text(a_str_reply, "alias not found");
+                    dap_string_free(l_string_reply, true);
+                    dap_chain_global_db_objs_delete(l_objs, l_nodes_count);
+                    return -1;
+                }
+                // read node
+                dap_chain_node_info_t *node_info_read = node_info_read_and_reply(a_net, address, NULL);
+                if(!node_info_read) {
+                    DAP_DELETE(address);
+                    continue;
+                    //dap_string_free(l_string_reply, true);
+                    //dap_chain_global_db_objs_delete(l_objs, l_nodes_count);
+                    //return -1;
+                }
+
+                const int hostlen = 128;
+                char *host4 = (char*) alloca(hostlen);
+                char *host6 = (char*) alloca(hostlen);
+                struct sockaddr_in sa4 = { .sin_family = AF_INET, .sin_addr = node_info_read->hdr.ext_addr_v4 };
+                const char* str_ip4 = inet_ntop(AF_INET, &(((struct sockaddr_in *) &sa4)->sin_addr), host4, hostlen);
+
+                struct sockaddr_in6 sa6 = { .sin6_family = AF_INET6, .sin6_addr = node_info_read->hdr.ext_addr_v6 };
+                const char* str_ip6 = inet_ntop(AF_INET6, &(((struct sockaddr_in6 *) &sa6)->sin6_addr), host6, hostlen);
+
+                // get aliases in form of string
+                dap_string_t *aliases_string = dap_string_new(NULL);
+                dap_list_t *list_aliases = get_aliases_by_name(a_net, address);
+                if(list_aliases)
+                {
+                    dap_list_t *list = list_aliases;
+                    while(list)
+                    {
+                        const char *alias = (const char *) list->data;
+                        dap_string_append_printf(aliases_string, "\nalias %s", alias);
+                        list = dap_list_next(list);
+                    }
+                    dap_list_free_full(list_aliases, (dap_callback_destroyed_t) free);
+                }
+                else
+                    dap_string_append(aliases_string, "\nno aliases");
+
+                // get links in form of string
+                dap_string_t *links_string = dap_string_new(NULL);
+                for(unsigned int i = 0; i < node_info_read->hdr.links_number; i++) {
+                    dap_chain_node_addr_t link_addr = node_info_read->links[i];
+                    dap_string_append_printf(links_string, "\nlink%02d address : " NODE_ADDR_FP_STR, i,
+                            NODE_ADDR_FP_ARGS_S(link_addr));
+                }
+
+                if(i)
+                    dap_string_append_printf(l_string_reply, "\n");
+                char l_port_str[10];
+                sprintf(l_port_str,"%d",node_info_read->hdr.ext_port);
+                // set short reply with node param
+                if(!a_is_full)
+                    dap_string_append_printf(l_string_reply,
+                            "node address "NODE_ADDR_FP_STR"\tcell 0x%016llx\tipv4 %s\tport: %s\tnumber of links %u",
+                            NODE_ADDR_FP_ARGS_S(node_info_read->hdr.address),
+                            node_info_read->hdr.cell_id.uint64, str_ip4,
+                            node_info_read->hdr.ext_port ? l_port_str : "default",
+                            node_info_read->hdr.links_number);
+                else
+                    // set full reply with node param
+                    dap_string_append_printf(l_string_reply,
+                            "node address " NODE_ADDR_FP_STR "\ncell 0x%016llx\nipv4 %s\nipv6 %s\nport: %s%s\nlinks %u%s",
+                            NODE_ADDR_FP_ARGS_S(node_info_read->hdr.address),
+                            node_info_read->hdr.cell_id.uint64,
+                            str_ip4, str_ip6,
+                            node_info_read->hdr.ext_port ? l_port_str : "default",
+                            aliases_string->str,
+                            node_info_read->hdr.links_number, links_string->str);
+                dap_string_free(aliases_string, true);
+                dap_string_free(links_string, true);
+
+                DAP_DELETE(address);
+                DAP_DELETE(node_info_read);
+            }
+        }
+        dap_chain_global_db_objs_delete(l_objs, l_nodes_count);
+    }
+    dap_chain_node_cli_set_reply_text(a_str_reply, l_string_reply->str);
+    dap_string_free(l_string_reply, true);
+    return l_ret;
+}
+
+/**
+ * global_db command
+ *
+ * return 0 OK, -1 Err
+ */
+
+int com_global_db(int a_argc, char ** a_argv, void *arg_func, char **a_str_reply)
+{
+    enum {
+        CMD_NONE, CMD_NAME_CELL, CMD_ADD, CMD_FLUSH
+    };
+    int arg_index = 1;
+    int cmd_name = CMD_NONE;
+    // find 'cells' as first parameter only
+    if(dap_chain_node_cli_find_option_val(a_argv, arg_index, min(a_argc, arg_index + 1), "cells", NULL))
+        cmd_name = CMD_NAME_CELL;
+    else if(dap_chain_node_cli_find_option_val(a_argv, arg_index, min(a_argc, arg_index + 1), "flush", NULL))
+        cmd_name = CMD_FLUSH;
+    switch (cmd_name) {
+    case CMD_NAME_CELL:
+    {
+        if(!arg_index || a_argc < 3) {
+            dap_chain_node_cli_set_reply_text(a_str_reply, "parameters are not valid");
+            return -1;
+        }
+        dap_chain_t * l_chain = NULL;
+        dap_chain_net_t * l_net = NULL;
+
+        if(dap_chain_node_cli_cmd_values_parse_net_chain(&arg_index, a_argc, a_argv, a_str_reply, &l_chain, &l_net) < 0)
+            return -11;
+
+        const char *l_cell_str = NULL, *l_chain_str = NULL;
+        // find cell and chain
+        dap_chain_node_cli_find_option_val(a_argv, arg_index, a_argc, "-cell", &l_cell_str);
+        dap_chain_node_cli_find_option_val(a_argv, arg_index, a_argc, "-chain", &l_chain_str);
+
+        // Check for chain
+        if(!l_chain_str) {
+            dap_chain_node_cli_set_reply_text(a_str_reply, "%s requires parameter 'chain' to be valid");
+            return -12;
+        }
+
+        int arg_index_n = ++arg_index;
+        // find command (add, delete, etc) as second parameter only
+        int cmd_num = CMD_NONE;
+        switch (cmd_name) {
+            case CMD_NAME_CELL:
+                if((arg_index_n = dap_chain_node_cli_find_option_val(a_argv, arg_index, min(a_argc, arg_index + 1), "add", NULL))
+                        != 0) {
+                    cmd_num = CMD_ADD;
+                }
+                dap_chain_cell_id_t l_cell_id = { {0} };
+                if(l_cell_str) {
+                    dap_digit_from_string(l_cell_str, (uint8_t*) &l_cell_id.raw, sizeof(l_cell_id.raw)); //DAP_CHAIN_CELL_ID_SIZE);
+                }
+
+                switch (cmd_num)
+                {
+                // add new node to global_db
+                case CMD_ADD:
+                    if(!arg_index || a_argc < 7) {
+                        dap_chain_node_cli_set_reply_text(a_str_reply, "invalid parameters");
+                        return -1;
+                    }
+                    dap_chain_cell_t *l_cell = dap_chain_cell_create();
+                    l_cell->chain = l_chain;
+                    l_cell->id.uint64 = l_cell_id.uint64;
+                    l_cell->file_storage_path = dap_strdup_printf("%0llx.dchaincell",l_cell->id.uint64);
+                    int l_ret = dap_chain_cell_file_update(l_cell);
+                    if(!l_ret)
+                        dap_chain_node_cli_set_reply_text(a_str_reply, "cell added successfully");
+                    else
+                        dap_chain_node_cli_set_reply_text(a_str_reply, "can't create file for cell 0x%016X ( %s )",
+                                l_cell->id.uint64,l_cell->file_storage_path);
+                    dap_chain_cell_delete(l_cell);
+                    return l_ret;
+
+                //case CMD_NONE:
+                default:
+                    dap_chain_node_cli_set_reply_text(a_str_reply, "command %s not recognized", a_argv[1]);
+                    return -1;
+                }
+        }
+    }
+    case CMD_FLUSH:
+    {
+        int res_flush = dap_chain_global_db_flush();
+        switch (res_flush) {
+        case 0:
+            dap_chain_node_cli_set_reply_text(a_str_reply, "Commit data base and filesystem caches to disk completed.\n\n");
+            break;
+        case -1:
+            dap_chain_node_cli_set_reply_text(a_str_reply, "Couldn't open db directory. Can't init cdb\n"
+                                                           "Reboot the node.\n\n");
+            break;
+        case -2:
+            dap_chain_node_cli_set_reply_text(a_str_reply, "Can't init cdb\n"
+                                                           "Reboot the node.\n\n");
+            break;
+        case -3:
+            dap_chain_node_cli_set_reply_text(a_str_reply, "Can't init sqlite\n"
+                                                           "Reboot the node.\n\n");
+            break;
+        default:
+            dap_chain_node_cli_set_reply_text(a_str_reply, "Can't commit data base caches to disk completed.\n"
+                                                           "Reboot the node.\n\n");
+            break;
+        }
+        return 0;
+    }
+    default:
+        dap_chain_node_cli_set_reply_text(a_str_reply, "parameters are not valid");
+        return -1;
+    }
+    return  -555;
+}
+
+/**
+ * Node command
+ */
+int com_node(int a_argc, char ** a_argv, void *arg_func, char **a_str_reply)
+{
+    enum {
+        CMD_NONE, CMD_ADD, CMD_DEL, CMD_LINK, CMD_ALIAS, CMD_HANDSHAKE, CMD_CONNECT, CMD_DUMP
+    };
+    int arg_index = 1;
+    int cmd_num = CMD_NONE;
+    if(dap_chain_node_cli_find_option_val(a_argv, arg_index, min(a_argc, arg_index + 1), "add", NULL)) {
+        cmd_num = CMD_ADD;
+    }
+    else if(dap_chain_node_cli_find_option_val(a_argv, arg_index, min(a_argc, arg_index + 1), "del", NULL)) {
+        cmd_num = CMD_DEL;
+    }
+    else if(dap_chain_node_cli_find_option_val(a_argv, arg_index, min(a_argc, arg_index + 1), "link", NULL)) {
+        cmd_num = CMD_LINK;
+    }
+    else
+    // find  add parameter ('alias' or 'handshake')
+    if(dap_chain_node_cli_find_option_val(a_argv, arg_index, min(a_argc, arg_index + 1), "handshake", NULL)) {
+        cmd_num = CMD_HANDSHAKE;
+    }
+    else if(dap_chain_node_cli_find_option_val(a_argv, arg_index, min(a_argc, arg_index + 1), "connect", NULL)) {
+        cmd_num = CMD_CONNECT;
+    }
+    else if(dap_chain_node_cli_find_option_val(a_argv, arg_index, min(a_argc, arg_index + 1), "alias", NULL)) {
+        cmd_num = CMD_ALIAS;
+    }
+    else if(dap_chain_node_cli_find_option_val(a_argv, arg_index, min(a_argc, arg_index + 1), "dump", NULL)) {
+        cmd_num = CMD_DUMP;
+    }
+    arg_index++;
+    if(cmd_num == CMD_NONE) {
+        dap_chain_node_cli_set_reply_text(a_str_reply, "command %s not recognized", a_argv[1]);
+        return -1;
+    }
+    const char *l_addr_str = NULL, *l_port_str = NULL, *alias_str = NULL;
+    const char *l_cell_str = NULL, *l_link_str = NULL, *a_ipv4_str = NULL, *a_ipv6_str = NULL;
+
+    // find net
+    dap_chain_net_t *l_net = NULL;
+
+    if(dap_chain_node_cli_cmd_values_parse_net_chain(&arg_index, a_argc, a_argv, a_str_reply, NULL, &l_net) < 0)
+        return -11;
+
+    // find addr, alias
+    dap_chain_node_cli_find_option_val(a_argv, arg_index, a_argc, "-addr", &l_addr_str);
+    dap_chain_node_cli_find_option_val(a_argv, arg_index, a_argc, "-port", &l_port_str);
+    dap_chain_node_cli_find_option_val(a_argv, arg_index, a_argc, "-alias", &alias_str);
+    dap_chain_node_cli_find_option_val(a_argv, arg_index, a_argc, "-cell", &l_cell_str);
+    dap_chain_node_cli_find_option_val(a_argv, arg_index, a_argc, "-ipv4", &a_ipv4_str);
+    dap_chain_node_cli_find_option_val(a_argv, arg_index, a_argc, "-ipv6", &a_ipv6_str);
+    dap_chain_node_cli_find_option_val(a_argv, arg_index, a_argc, "-link", &l_link_str);
+
+    // struct to write to the global db
+    dap_chain_node_addr_t l_node_addr = { 0 };
+    dap_chain_node_addr_t l_link = { 0 };
+    dap_chain_node_info_t *l_node_info = NULL;
+    size_t l_node_info_size = sizeof(l_node_info->hdr) + sizeof(l_link);
+    if(cmd_num >= CMD_ADD && cmd_num <= CMD_LINK)
+        l_node_info = DAP_NEW_Z_SIZE(dap_chain_node_info_t, l_node_info_size);
+
+    if(l_addr_str) {
+        if(dap_chain_node_addr_from_str(&l_node_addr, l_addr_str) != 0) {
+            dap_digit_from_string(l_addr_str, l_node_addr.raw, sizeof(l_node_addr.raw));
+        }
+        if(l_node_info)
+            memcpy(&l_node_info->hdr.address, &l_node_addr, sizeof(dap_chain_node_addr_t));
+    }
+    if(l_port_str) {
+        uint16_t l_node_port = 0;
+        dap_digit_from_string(l_port_str, &l_node_port, sizeof(uint16_t));
+        if(l_node_info)
+            l_node_info->hdr.ext_port = l_node_port;
+    }
+    if(l_cell_str && l_node_info) {
+        dap_digit_from_string(l_cell_str, l_node_info->hdr.cell_id.raw, sizeof(l_node_info->hdr.cell_id.raw)); //DAP_CHAIN_CELL_ID_SIZE);
+    }
+    if(l_link_str) {
+        if(dap_chain_node_addr_from_str(&l_link, l_link_str) != 0) {
+            dap_digit_from_string(l_link_str, l_link.raw, sizeof(l_link.raw));
+        }
+    }
+
+    switch (cmd_num)
+    {
+    case CMD_ADD:
+        if(!arg_index || a_argc < 8) {
+            dap_chain_node_cli_set_reply_text(a_str_reply, "invalid parameters");
+            return -1;
+        }
+        // handler of command 'node add'
+        int l_ret = node_info_add_with_reply(l_net, l_node_info, alias_str, l_cell_str, a_ipv4_str, a_ipv6_str,
+                a_str_reply);
+        DAP_DELETE(l_node_info);
+        return l_ret;
+        //break;
+
+    case CMD_DEL:
+        // handler of command 'node del'
+    {
+        int l_ret = node_info_del_with_reply(l_net, l_node_info, alias_str, a_str_reply);
+        DAP_DELETE(l_node_info);
+        return l_ret;
+    }
+    case CMD_LINK:
+        if(dap_chain_node_cli_find_option_val(a_argv, arg_index, min(a_argc, arg_index + 1), "add", NULL)) {
+            // handler of command 'node link add -addr <node address> -link <node address>'
+            int l_ret = link_add_or_del_with_reply(l_net, l_node_info, "add", alias_str, &l_link, a_str_reply);
+            DAP_DELETE(l_node_info);
+            return l_ret;
+        }
+        else if(dap_chain_node_cli_find_option_val(a_argv, arg_index, min(a_argc, arg_index + 1), "del", NULL)) {
+            // handler of command 'node link del -addr <node address> -link <node address>'
+            int l_ret = link_add_or_del_with_reply(l_net, l_node_info, "del", alias_str, &l_link, a_str_reply);
+            DAP_DELETE(l_node_info);
+            return l_ret;
+        }
+        else {
+            dap_chain_node_cli_set_reply_text(a_str_reply, "command not recognize, supported format:\n"
+                    "global_db node link <add|del] [-addr <node address>  | -alias <node alias>] -link <node address>");
+            DAP_DELETE(l_node_info);
+            return -1;
+        }
+
+    case CMD_DUMP: {
+        // handler of command 'node dump'
+        bool l_is_full = dap_chain_node_cli_find_option_val(a_argv, arg_index, a_argc, "-full", NULL);
+        return node_info_dump_with_reply(l_net, &l_node_addr, l_is_full, alias_str, a_str_reply);
+    }
+        // add alias
+    case CMD_ALIAS:
+        if(alias_str) {
+            if(l_addr_str) {
+                // add alias
+                if(!dap_chain_node_alias_register(l_net, alias_str, &l_node_addr))
+                    log_it(L_WARNING, "can't save alias %s", alias_str);
+                else {
+                    dap_chain_node_cli_set_reply_text(a_str_reply, "alias mapped successfully");
+                }
+            }
+            else {
+                dap_chain_node_cli_set_reply_text(a_str_reply, "alias can't be mapped because -addr is not found");
+                return -1;
+            }
+        }
+        else {
+            dap_chain_node_cli_set_reply_text(a_str_reply, "alias can't be mapped because -alias is not found");
+            return -1;
+        }
+
+        break;
+        // make connect
+    case CMD_CONNECT: {
+        // get address from alias if addr not defined
+        if(alias_str && !l_node_addr.uint64) {
+            dap_chain_node_addr_t *address_tmp = dap_chain_node_addr_get_by_alias(l_net, alias_str);
+            if(address_tmp) {
+                memcpy(&l_node_addr, address_tmp, sizeof(*address_tmp));
+                DAP_DELETE(address_tmp);
+            }
+            else {
+                dap_chain_node_cli_set_reply_text(a_str_reply, "no address found by alias");
+                return -1;
+            }
+        }
+        // for auto mode
+        int l_is_auto = 0;
+        // list of dap_chain_node_addr_t struct
+        unsigned int l_nodes_count = 0;
+        dap_list_t *l_node_list = NULL;
+        dap_chain_node_addr_t *l_remote_node_addr = NULL;
+        if(!l_node_addr.uint64) {
+            // check whether auto mode
+            l_is_auto = dap_chain_node_cli_find_option_val(a_argv, arg_index, a_argc, "auto", NULL);
+            if(!l_is_auto) {
+                dap_chain_node_cli_set_reply_text(a_str_reply, "addr not found");
+                return -1;
+            }
+            // if auto mode, then looking for the node address
+
+            // get cur node links
+            bool a_is_only_cur_cell = false;
+            dap_list_t *l_node_link_list = dap_chain_net_get_link_node_list(l_net, a_is_only_cur_cell);
+            // get all nodes list if no links
+            if(!l_node_link_list)
+                l_node_list = dap_chain_net_get_node_list(l_net);
+            else
+                l_node_list = dap_list_concat(l_node_link_list, l_node_list);
+
+            // select random node from the list
+            l_nodes_count = dap_list_length(l_node_list);
+            if(l_nodes_count > 0) {
+                unsigned int l_node_pos = rand() % l_nodes_count;
+                dap_list_t *l_tmp = dap_list_nth(l_node_list, l_node_pos);
+                l_remote_node_addr = l_tmp->data;
+                l_node_addr.uint64 = l_remote_node_addr->uint64;
+            }
+
+            if(!l_node_addr.uint64) {
+                dap_chain_node_cli_set_reply_text(a_str_reply, "no node is available");
+                return -1;
+            }
+        }
+        dap_chain_node_info_t *l_remote_node_info;
+        dap_chain_node_client_t *l_node_client;
+        int res;
+        do {
+            l_remote_node_info = node_info_read_and_reply(l_net, &l_node_addr, a_str_reply);
+            if(!l_remote_node_info) {
+                return -1;
+            }
+            // start connect
+            l_node_client = dap_chain_node_client_connect(l_remote_node_info);
+            if(!l_node_client) {
+                dap_chain_node_cli_set_reply_text(a_str_reply, "can't connect");
+                DAP_DELETE(l_remote_node_info);
+                return -1;
+            }
+            // wait connected
+            int timeout_ms = 7000; // 7 sec = 7000 ms
+            res = dap_chain_node_client_wait(l_node_client, NODE_CLIENT_STATE_CONNECTED, timeout_ms);
+            // select new node addr
+            if(l_is_auto && res){
+                if(l_remote_node_addr && l_nodes_count>1){
+                    l_nodes_count--;
+                    l_node_list = dap_list_remove(l_node_list, l_remote_node_addr);
+                    DAP_DELETE(l_remote_node_addr);
+                    unsigned int l_node_pos = rand() % l_nodes_count;
+                    dap_list_t *l_tmp = dap_list_nth(l_node_list, l_node_pos);
+                    l_remote_node_addr = l_tmp->data;
+                    l_node_addr.uint64 = l_remote_node_addr->uint64;
+
+                    // clean client struct
+                    dap_chain_node_client_close(l_node_client);
+                    DAP_DELETE(l_remote_node_info);
+                    //return -1;
+                    continue;
+                }
+            }
+            break;
+        }
+        while(1);
+        // for auto mode only
+        if(l_is_auto) {
+            //start background thread for testing connect to the nodes
+            dap_chain_node_ping_background_start(l_net, l_node_list);
+            dap_list_free_full(l_node_list, free);
+        }
+
+
+
+        if(res) {
+            dap_chain_node_cli_set_reply_text(a_str_reply, "no response from remote node(s)");
+            log_it(L_WARNING, "No response from remote node(s): err code %d", res);
+            // clean client struct
+            dap_chain_node_client_close(l_node_client);
+            //DAP_DELETE(l_remote_node_info);
+            return -1;
+        }
+
+        log_it(L_NOTICE, "Stream connection established");
+        dap_stream_ch_chain_sync_request_t l_sync_request = { { 0 } };
+         dap_stream_ch_t * l_ch_chain = dap_client_get_stream_ch(l_node_client->client, dap_stream_ch_chain_get_id());
+         // fill begin id
+         l_sync_request.id_start = (uint64_t) dap_db_log_get_last_id_remote(
+                 l_remote_node_info->hdr.address.uint64);
+         // fill end id = 0 - no time limit
+         //l_sync_request.ts_end = 0;
+         // fill current node address
+         l_sync_request.node_addr.uint64 = dap_chain_net_get_cur_addr_int(l_net);
+
+        // if need to get current node address (feature-2630)
+        if(!l_sync_request.node_addr.uint64 )
+        {
+            log_it(L_NOTICE, "Now get node addr");
+            uint8_t l_ch_id = dap_stream_ch_chain_net_get_id();
+            dap_stream_ch_t * l_ch_chain = dap_client_get_stream_ch(l_node_client->client, l_ch_id);
+
+            int l_res = dap_chain_node_client_set_callbacks( l_node_client->client, l_ch_id);
+
+            size_t res = dap_stream_ch_chain_net_pkt_write(l_ch_chain,
+            DAP_STREAM_CH_CHAIN_NET_PKT_TYPE_NODE_ADDR_LEASE_REQUEST,
+            //DAP_STREAM_CH_CHAIN_NET_PKT_TYPE_NODE_ADDR_REQUEST,
+            l_net->pub.id,
+            NULL, 0);
+            if(res == 0) {
+                log_it(L_WARNING, "Can't send DAP_STREAM_CH_CHAIN_NET_PKT_TYPE_NODE_ADDR_REQUEST packet");
+                dap_chain_node_client_close(l_node_client);
+                DAP_DELETE(l_remote_node_info);
+                return -1;
+            }
+            int timeout_ms = 15000; // 15 sec = 15 000 ms
+            l_res = dap_chain_node_client_wait(l_node_client, NODE_CLIENT_STATE_NODE_ADDR_LEASED, timeout_ms);
+            switch (l_res) {
+            case 0:
+                if(l_node_client->cur_node_addr.uint64 != 0) {
+
+                    l_sync_request.node_addr.uint64 = l_node_client->cur_node_addr.uint64;
+                    log_it(L_INFO, "Node address leased");
+                    l_sync_request.node_addr.uint64 = l_node_client->cur_node_addr.uint64;
+                    // save cur address
+                    // already saved
+                    // dap_db_set_cur_node_addr_exp(l_sync_request.node_addr.uint64, l_net->pub.name);
+                }
+                else
+                    log_it(L_WARNING, "Node address leased wrong!");
+                break;
+            case -1:
+                log_it(L_WARNING, "Timeout with addr leasing");
+            default:
+                if(l_res != -1)
+                    log_it(L_WARNING, "Node address request error %d", l_res);
+                /*dap_chain_node_client_close(l_node_client);
+                DAP_DELETE(l_remote_node_info);
+                return -1;*/
+            }
+            /*                if(0 == dap_stream_ch_chain_pkt_write(l_ch_chain, DAP_STREAM_CH_CHAIN_NET_PKT_TYPE_NODE_ADDR_REQUEST,
+             l_net->pub.id, l_chain_id_null, l_chain_cell_id_null, &l_sync_request,
+             sizeof(l_sync_request))) {
+             dap_chain_node_cli_set_reply_text(a_str_reply, "Error: Cant send sync chains request");
+             // clean client struct
+             dap_chain_node_client_close(l_node_client);
+             DAP_DELETE(l_remote_node_info);
+             return -1;
+             }
+            dap_stream_ch_set_ready_to_write(l_ch_chain, true);
+            // wait for finishing of request
+            timeout_ms = 120000; // 20 min = 1200 sec = 1 200 000 ms
+            // TODO add progress info to console
+            res = dap_chain_node_client_wait(l_node_client, NODE_CLIENT_STATE_SYNCED, timeout_ms);
+            */
+
+        }
+        log_it(L_NOTICE, "Now lets sync all");
+
+        dap_chain_id_t l_chain_id_null = { { 0 } };
+        dap_chain_cell_id_t l_chain_cell_id_null = { { 0 } };
+        l_chain_id_null.uint64 = l_net->pub.id.uint64;
+        l_chain_cell_id_null.uint64 = dap_chain_net_get_cur_cell(l_net) ? dap_chain_net_get_cur_cell(l_net)->uint64 : 0;
+
+        log_it(L_INFO, "Requested GLOBAL_DB syncronizatoin, %llu:%llu period", l_sync_request.id_start,
+                l_sync_request.id_end);
+        // copy l_sync_request to current
+        //dap_stream_ch_chain_t * l_s_ch_chain = DAP_STREAM_CH_CHAIN(l_ch_chain);
+        //l_s_ch_chain->request_net_id.uint64 = l_net->pub.id.uint64;
+        //l_s_ch_chain->request_cell_id.uint64 = l_chain_cell_id_null.uint64;
+        //memcpy(&l_s_ch_chain->request, &l_sync_request, sizeof(l_sync_request));
+
+        if(0 == dap_stream_ch_chain_pkt_write(l_ch_chain, DAP_STREAM_CH_CHAIN_PKT_TYPE_SYNC_GLOBAL_DB,
+                l_net->pub.id, l_chain_id_null, l_chain_cell_id_null, &l_sync_request,
+                sizeof(l_sync_request))) {
+            dap_chain_node_cli_set_reply_text(a_str_reply, "Error: Can't send sync chains request");
+            // clean client struct
+            dap_chain_node_client_close(l_node_client);
+            DAP_DELETE(l_remote_node_info);
+            return -1;
+        }
+        dap_stream_ch_set_ready_to_write(l_ch_chain, true);
+        // wait for finishing of request
+        int timeout_ms = 420000; // 7 min = 420 sec = 420 000 ms
+        // TODO add progress info to console
+        res = dap_chain_node_client_wait(l_node_client, NODE_CLIENT_STATE_SYNCED, timeout_ms);
+        if(res < 0) {
+            dap_chain_node_client_close(l_node_client);
+            DAP_DELETE(l_remote_node_info);
+            dap_chain_node_cli_set_reply_text(a_str_reply, "Error: can't sync with node "NODE_ADDR_FP_STR,
+                    NODE_ADDR_FP_ARGS_S(l_node_client->remote_node_addr));
+
+            log_it(L_WARNING, "Gdb synced err -2");
+            return -2;
+
+        }
+        // flush global_db
+        dap_chain_global_db_flush();
+        log_it(L_INFO, "Gdb synced Ok");
+
+        // Requesting chains
+        dap_chain_t *l_chain = NULL;
+        DL_FOREACH(l_net->pub.chains, l_chain)
+        {
+            // reset state NODE_CLIENT_STATE_SYNCED
+            l_node_client->state = NODE_CLIENT_STATE_CONNECTED;
+            // send request
+            dap_stream_ch_chain_sync_request_t l_sync_request = { { 0 } };
+            if(0 == dap_stream_ch_chain_pkt_write(l_ch_chain, DAP_STREAM_CH_CHAIN_PKT_TYPE_SYNC_CHAINS,
+                    l_net->pub.id, l_chain->id, l_remote_node_info->hdr.cell_id, &l_sync_request,
+                    sizeof(l_sync_request))) {
+                dap_chain_node_cli_set_reply_text(a_str_reply, "Error: Can't send sync chains request");
+                // clean client struct
+                dap_chain_node_client_close(l_node_client);
+                DAP_DELETE(l_remote_node_info);
+                log_it(L_INFO, "Chain '%s' synced error: Can't send sync chains request", l_chain->name);
+                return -3;
+            }
+            log_it(L_NOTICE, "Requested syncronization for chain \"%s\"", l_chain->name);
+            dap_stream_ch_set_ready_to_write(l_ch_chain, true);
+
+            // wait for finishing of request
+            timeout_ms = 120000; // 2 min = 120 sec = 120 000 ms
+            // TODO add progress info to console
+            res = dap_chain_node_client_wait(l_node_client, NODE_CLIENT_STATE_SYNCED, timeout_ms);
+            if(res < 0) {
+                log_it(L_ERROR, "Error: Can't sync chain %s", l_chain->name);
+            }
+        }
+        log_it(L_INFO, "Chains and gdb are synced");
+        DAP_DELETE(l_remote_node_info);
+        //dap_client_disconnect(l_node_client->client);
+        //l_node_client->client = NULL;
+        dap_chain_node_client_close(l_node_client);
+        dap_chain_node_cli_set_reply_text(a_str_reply, "Node sync completed: Chains and gdb are synced");
+        return 0;
+
+    }
+        // make handshake
+    case CMD_HANDSHAKE: {
+        // get address from alias if addr not defined
+        if(alias_str && !l_node_addr.uint64) {
+            dap_chain_node_addr_t *address_tmp = dap_chain_node_addr_get_by_alias(l_net, alias_str);
+            if(address_tmp) {
+                memcpy(&l_node_addr, address_tmp, sizeof(*address_tmp));
+                DAP_DELETE(address_tmp);
+            }
+            else {
+                dap_chain_node_cli_set_reply_text(a_str_reply, "no address found by alias");
+                return -4;
+            }
+        }
+        if(!l_node_addr.uint64) {
+            dap_chain_node_cli_set_reply_text(a_str_reply, "addr not found");
+            return -5;
+        }
+
+        dap_chain_node_info_t *node_info = node_info_read_and_reply(l_net, &l_node_addr, a_str_reply);
+        if(!node_info)
+            return -6;
+        int timeout_ms = 5000; //5 sec = 5000 ms
+        // start handshake
+        dap_chain_node_client_t *client = dap_chain_node_client_connect(node_info);
+        if(!client) {
+            dap_chain_node_cli_set_reply_text(a_str_reply, "can't connect");
+            DAP_DELETE(node_info);
+            return -7;
+        }
+        // wait handshake
+        int res = dap_chain_node_client_wait(client, NODE_CLIENT_STATE_CONNECTED, timeout_ms);
+        if(res != 1) {
+            dap_chain_node_cli_set_reply_text(a_str_reply, "no response from node");
+            // clean client struct
+            dap_chain_node_client_close(client);
+            DAP_DELETE(node_info);
+            return -8;
+        }
+        DAP_DELETE(node_info);
+
+        //Add new established connection in the list
+        int ret = dap_chain_node_client_list_add(&l_node_addr, client);
+        switch (ret)
+        {
+        case -1:
+            dap_chain_node_client_close(client);
+            dap_chain_node_cli_set_reply_text(a_str_reply, "connection established, but not saved");
+            return -9;
+        case -2:
+            dap_chain_node_client_close(client);
+            dap_chain_node_cli_set_reply_text(a_str_reply, "connection already present");
+            return -10;
+        }
+        dap_chain_node_cli_set_reply_text(a_str_reply, "connection established");
+    }
+        break;
+    }
+    return 0;
+}
+
+/**
+ * Traceroute command
+ *
+ * return 0 OK, -1 Err
+ */
+int com_traceroute(int argc, char** argv, void *arg_func, char **str_reply)
+{
+#ifndef _WIN32
+    const char *addr = NULL;
+    int hops = 0, time_usec = 0;
+    if(argc > 1)
+        addr = argv[1];
+    iputils_set_verbose();
+    int res = (addr) ? traceroute_util(addr, &hops, &time_usec) : -EADDRNOTAVAIL;
+    if(res >= 0) {
+        dap_chain_node_cli_set_reply_text(str_reply, "traceroute %s hops=%d time=%.1lf ms", addr, hops,
+                time_usec * 1. / 1000);
+    }
+    else {
+        if(str_reply) {
+            switch (-res)
+            {
+            case EADDRNOTAVAIL:
+                dap_chain_node_cli_set_reply_text(str_reply, "traceroute %s error: %s", (addr) ? addr : "",
+                        (addr) ? "Name or service not known" : "Host not defined");
+                break;
+            case 2:
+                dap_chain_node_cli_set_reply_text(str_reply, "traceroute %s error: %s", addr,
+                        "Unknown traceroute module");
+                break;
+            case 3:
+                dap_chain_node_cli_set_reply_text(str_reply, "traceroute %s error: %s", addr, "first hop out of range");
+                break;
+            case 4:
+                dap_chain_node_cli_set_reply_text(str_reply, "traceroute %s error: %s", addr,
+                        "max hops cannot be more than 255");
+                break;
+            case 5:
+                dap_chain_node_cli_set_reply_text(str_reply, "traceroute %s error: %s", addr,
+                        "no more than 10 probes per hop");
+                break;
+            case 6:
+                dap_chain_node_cli_set_reply_text(str_reply, "traceroute %s error: %s", addr,
+                        "bad wait specifications");
+                break;
+            case 7:
+                dap_chain_node_cli_set_reply_text(str_reply, "traceroute %s error: %s", addr, "too big packetlen ");
+                break;
+            case 8:
+                dap_chain_node_cli_set_reply_text(str_reply, "traceroute %s error: %s", addr,
+                        "IP version mismatch in addresses specified");
+                break;
+            case 9:
+                dap_chain_node_cli_set_reply_text(str_reply, "traceroute %s error: %s", addr, "bad sendtime");
+                break;
+            case 10:
+                dap_chain_node_cli_set_reply_text(str_reply, "traceroute %s error: %s", addr, "init_ip_options");
+                break;
+            case 11:
+                dap_chain_node_cli_set_reply_text(str_reply, "traceroute %s error: %s", addr, "calloc");
+                break;
+            case 12:
+                dap_chain_node_cli_set_reply_text(str_reply, "traceroute %s error: %s", addr, "parse cmdline");
+                break;
+            case 13:
+                dap_chain_node_cli_set_reply_text(str_reply, "traceroute %s error: %s", addr,
+                        "trace method's init failed");
+                break;
+            default:
+                dap_chain_node_cli_set_reply_text(str_reply, "traceroute %s error(%d) %s", addr, res,
+                        "trace not found");
+            }
+        }
+    }
+    return res;
+#endif
+    return 0;
+}
+
+/**
+ * Tracepath command
+ *
+ * return 0 OK, -1 Err
+ */
+int com_tracepath(int argc, char** argv, void *arg_func, char **str_reply)
+{
+#ifndef _WIN32
+    const char *addr = NULL;
+    int hops = 0, time_usec = 0;
+    if(argc > 1)
+        addr = argv[1];
+    iputils_set_verbose();
+    int res = (addr) ? tracepath_util(addr, &hops, &time_usec) : -EADDRNOTAVAIL;
+    if(res >= 0) {
+        if(str_reply)
+            dap_chain_node_cli_set_reply_text(str_reply, "tracepath %s hops=%d time=%.1lf ms", addr, hops,
+                    time_usec * 1. / 1000);
+    }
+    else {
+        if(str_reply) {
+            switch (-res)
+            {
+            case EADDRNOTAVAIL:
+                dap_chain_node_cli_set_reply_text(str_reply, "tracepath %s error: %s", (addr) ? addr : "",
+                        (addr) ? "Name or service not known" : "Host not defined");
+                break;
+            case ESOCKTNOSUPPORT:
+                dap_chain_node_cli_set_reply_text(str_reply, "tracepath %s error: %s", addr, "Can't create socket");
+                break;
+            case 2:
+                dap_chain_node_cli_set_reply_text(str_reply, "tracepath %s error: %s", addr,
+                        "Can't setsockopt IPV6_MTU_DISCOVER");
+                break;
+            case 3:
+                dap_chain_node_cli_set_reply_text(str_reply, "tracepath %s error: %s", addr,
+                        "Can't setsockopt IPV6_RECVERR");
+                break;
+            case 4:
+                dap_chain_node_cli_set_reply_text(str_reply, "tracepath %s error: %s", addr,
+                        "Can't setsockopt IPV6_HOPLIMIT");
+                break;
+            case 5:
+                dap_chain_node_cli_set_reply_text(str_reply, "tracepath %s error: %s", addr,
+                        "Can't setsockopt IP_MTU_DISCOVER");
+                break;
+            case 6:
+                dap_chain_node_cli_set_reply_text(str_reply, "tracepath %s error: %s", addr,
+                        "Can't setsockopt IP_RECVERR");
+                break;
+            case 7:
+                dap_chain_node_cli_set_reply_text(str_reply, "tracepath %s error: %s", addr,
+                        "Can't setsockopt IP_RECVTTL");
+                break;
+            case 8:
+                dap_chain_node_cli_set_reply_text(str_reply, "tracepath %s error: %s", addr, "malloc");
+                break;
+            case 9:
+                dap_chain_node_cli_set_reply_text(str_reply, "tracepath %s error: %s", addr,
+                        "Can't setsockopt IPV6_UNICAST_HOPS");
+                break;
+            case 10:
+                dap_chain_node_cli_set_reply_text(str_reply, "tracepath %s error: %s", addr, "Can't setsockopt IP_TTL");
+                break;
+            default:
+                dap_chain_node_cli_set_reply_text(str_reply, "tracepath %s error(%d) %s", addr, res, "trace not found");
+            }
+        }
+    }
+    return res;
+#endif
+    return 0;
+}
+
+/**
+ * Ping command
+ *
+ * return 0 OK, -1 Err
+ */
+int com_ping(int argc, char** argv, void *arg_func, char **str_reply)
+{
+#ifndef _WIN32
+
+    int n = 4;
+    if(argc < 2) {
+        dap_chain_node_cli_set_reply_text(str_reply, "host not specified");
+        return -1;
+    }
+    const char *n_str = NULL;
+    int argc_host = 1;
+    int argc_start = 1;
+    argc_start = dap_chain_node_cli_find_option_val(argv, argc_start, argc, "-n", &n_str);
+    if(argc_start) {
+        argc_host = argc_start + 1;
+        n = (n_str) ? atoi(n_str) : 4;
+    }
+    else {
+        argc_start = dap_chain_node_cli_find_option_val(argv, argc_start, argc, "-c", &n_str);
+        if(argc_start) {
+            argc_host = argc_start + 1;
+            n = (n_str) ? atoi(n_str) : 4;
+        }
+    }
+    if(n <= 1)
+        n = 1;
+    const char *addr = argv[argc_host];
+    iputils_set_verbose();
+    ping_handle_t *l_ping_handle = ping_handle_create();
+    int res = (addr) ? ping_util(l_ping_handle, addr, n) : -EADDRNOTAVAIL;
+    DAP_DELETE(l_ping_handle);
+    if(res >= 0) {
+        if(str_reply)
+            dap_chain_node_cli_set_reply_text(str_reply, "ping %s time=%.1lf ms", addr, res * 1. / 1000);
+    }
+    else {
+        if(str_reply) {
+            switch (-res)
+            {
+            case EDESTADDRREQ:
+                dap_chain_node_cli_set_reply_text(str_reply, "ping %s error: %s", addr, "Destination address required");
+                break;
+            case EADDRNOTAVAIL:
+                dap_chain_node_cli_set_reply_text(str_reply, "ping %s error: %s", (addr) ? addr : "",
+                        (addr) ? "Host not found" : "Host not defined");
+                break;
+            case EPFNOSUPPORT:
+                dap_chain_node_cli_set_reply_text(str_reply, "ping %s error: %s", addr, "Unknown protocol family");
+                break;
+            default:
+                dap_chain_node_cli_set_reply_text(str_reply, "ping %s error(%d)", addr, -res);
+            }
+        }
+    }
+    return res;
+#endif
+    return 0;
+}
+
+/**
+ * Help command
+ */
+int com_help(int argc, char ** argv, void *arg_func, char **str_reply)
+{
+    if(argc > 1) {
+        log_it(L_DEBUG, "Help for command %s", argv[1]);
+        dap_chain_node_cmd_item_t *l_cmd = dap_chain_node_cli_cmd_find(argv[1]);
+        if(l_cmd) {
+            dap_chain_node_cli_set_reply_text(str_reply, "%s:\n%s", l_cmd->doc, l_cmd->doc_ex);
+            return 0;
+        } else {
+            dap_chain_node_cli_set_reply_text(str_reply, "command \"%s\" not recognized", argv[1]);
+        }
+        return -1;
+    } else {
+        // TODO Read list of commands & return it
+        log_it(L_DEBUG, "General help requested");
+        dap_string_t * l_help_list_str = dap_string_new(NULL);
+        dap_chain_node_cmd_item_t *l_cmd = dap_chain_node_cli_cmd_get_first();
+        dap_string_printf(l_help_list_str, "");
+        while(l_cmd) {
+            dap_string_append_printf(l_help_list_str, "%s:\t\t\t%s\n",
+                    l_cmd->name, l_cmd->doc ? l_cmd->doc : "(undocumented command)");
+            l_cmd = (dap_chain_node_cmd_item_t*) l_cmd->hh.next;
+        }
+        dap_chain_node_cli_set_reply_text(str_reply,
+                "Available commands:\n\n%s\n",
+                l_help_list_str->len ? l_help_list_str->str : "NO ANY COMMAND WERE DEFINED");
+        return 0;
+    }
+}
+
+/**
+ * com_tx_create command
+ *
+ * Wallet info
+ */
+int com_tx_wallet(int argc, char ** argv, void *arg_func, char **str_reply)
+{
+    const char *c_wallets_path = dap_chain_wallet_get_path(g_config);
+    // Get address of wallet
+    enum {
+        CMD_NONE, CMD_WALLET_NEW, CMD_WALLET_LIST, CMD_WALLET_INFO
+    };
+    int arg_index = 1;
+    int cmd_num = CMD_NONE;
+    // find  add parameter ('alias' or 'handshake')
+    if(dap_chain_node_cli_find_option_val(argv, arg_index, min(argc, arg_index + 1), "new", NULL)) {
+        cmd_num = CMD_WALLET_NEW;
+    }
+    else if(dap_chain_node_cli_find_option_val(argv, arg_index, min(argc, arg_index + 1), "list", NULL)) {
+        cmd_num = CMD_WALLET_LIST;
+    }
+    else if(dap_chain_node_cli_find_option_val(argv, arg_index, min(argc, arg_index + 1), "info", NULL)) {
+        cmd_num = CMD_WALLET_INFO;
+    }
+    arg_index++;
+    if(cmd_num == CMD_NONE) {
+        dap_chain_node_cli_set_reply_text(str_reply,
+                "format of command: wallet [new -w <wallet_name> | list | info -addr <addr> -w <wallet_name> -net <net_name>]");
+        return -1;
+    }
+
+    dap_chain_node_addr_t address;
+    memset(&address, 0, sizeof(dap_chain_node_addr_t));
+    const char *l_addr_str = NULL, *l_wallet_name = NULL, *l_net_name = NULL, *l_sign_type_str = NULL, *l_restore_str = NULL;
+    // find wallet addr
+    dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-addr", &l_addr_str);
+    dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-w", &l_wallet_name);
+    dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-net", &l_net_name);
+
+    dap_chain_net_t * l_net = l_net_name ? dap_chain_net_by_name( l_net_name) : NULL;
+
+    dap_string_t *l_string_ret = dap_string_new(NULL);
+    switch (cmd_num) {
+    // new wallet
+    case CMD_WALLET_NEW: {
+        dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-sign", &l_sign_type_str);
+        dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-restore", &l_restore_str);
+        // rewrite existing wallet
+        int l_is_force = dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-force", NULL);
+
+        if(!l_wallet_name) {
+            dap_chain_node_cli_set_reply_text(str_reply, "wallet name option <-w>  not defined");
+            return -1;
+        }
+        // check wallet existence
+        if(!l_is_force) {
+            dap_chain_wallet_t *l_wallet = dap_chain_wallet_open(l_wallet_name, c_wallets_path);
+            if(l_wallet) {
+                dap_chain_node_cli_set_reply_text(str_reply, "wallet already exists");
+                dap_chain_wallet_close(l_wallet);
+                return -1;
+            }
+        }
+
+//        dap_sign_type_t l_sign_type = { SIG_TYPE_BLISS };
+        dap_sign_type_t l_sign_type = dap_sign_type_from_str(l_sign_type_str);
+        if(l_sign_type.type == SIG_TYPE_NULL){
+            l_sign_type.type = SIG_TYPE_DILITHIUM;
+            l_sign_type_str = dap_sign_type_to_str(l_sign_type);
+        }
+        uint8_t *l_seed = NULL;
+        size_t l_seed_size = 0;
+        size_t l_restore_str_size = dap_strlen(l_restore_str);
+        if(l_restore_str && l_restore_str_size > 2 && !dap_strncmp(l_restore_str, "0x", 2)) {
+            l_seed_size = (l_restore_str_size - 2) / 2;
+            l_seed = DAP_NEW_SIZE(uint8_t, l_seed_size);
+            if(!dap_hex2bin(l_seed, l_restore_str + 2, l_restore_str_size - 2)){
+                DAP_DELETE(l_seed);
+                l_seed = NULL;
+                l_seed_size = 0;
+            }
+        }
+        // Creates new wallet
+        dap_chain_wallet_t *l_wallet = dap_chain_wallet_create_with_seed(l_wallet_name, c_wallets_path, l_sign_type,
+                l_seed, l_seed_size);
+        dap_chain_addr_t *l_addr = l_net? dap_chain_wallet_get_addr(l_wallet,l_net->pub.id ) : NULL;
+        if(!l_wallet) {
+            dap_chain_node_cli_set_reply_text(str_reply, "wallet is not created");
+            return -1;
+        }
+        char *l_addr_str = l_addr? dap_chain_addr_to_str(l_addr) : NULL;
+        dap_string_append_printf(l_string_ret, "wallet '%s' (type=%s) successfully created\n", l_wallet->name, l_sign_type_str);
+        if ( l_addr_str )
+            dap_string_append_printf(l_string_ret, "new address %s", l_addr_str);
+        DAP_DELETE(l_addr_str);
+        dap_chain_wallet_close(l_wallet);
+    }
+        break;
+        // wallet list
+    case CMD_WALLET_LIST: {
+        DIR * l_dir = opendir(c_wallets_path);
+        if(l_dir) {
+            struct dirent * l_dir_entry;
+            while((l_dir_entry = readdir(l_dir)) != NULL) {
+                const char *l_file_name = l_dir_entry->d_name;
+                size_t l_file_name_len = (l_file_name) ? strlen(l_file_name) : 0;
+                if((l_file_name_len > 8) && (strcmp(l_file_name + l_file_name_len - 8, ".dwallet") == 0)) {
+                    char *l_file_path_tmp = dap_strdup_printf("%s/%s", c_wallets_path, l_file_name);
+                    dap_chain_wallet_t *l_wallet = dap_chain_wallet_open_file(l_file_path_tmp);
+                    if(l_wallet) {
+                        dap_chain_addr_t *l_addr = l_net? dap_chain_wallet_get_addr(l_wallet, l_net->pub.id) : NULL;
+                        char *l_addr_str = dap_chain_addr_to_str(l_addr);
+                        dap_string_append_printf(l_string_ret, "\nwallet: %s\n", l_wallet->name);
+                        if ( l_addr_str){
+                            dap_string_append_printf(l_string_ret, "addr: %s\n", (l_addr_str) ? l_addr_str : "-");
+                            DAP_DELETE(l_addr_str);
+                        }
+                        dap_chain_wallet_close(l_wallet);
+                    }
+                    DAP_DELETE(l_file_path_tmp);
+                }
+            }
+            closedir(l_dir);
+        }
+    }
+        break;
+
+        // wallet info
+    case CMD_WALLET_INFO: {
+        dap_chain_wallet_t *l_wallet = NULL;
+        dap_chain_addr_t *l_addr = NULL;
+
+        if(l_wallet_name) {
+            l_wallet = dap_chain_wallet_open(l_wallet_name, c_wallets_path);
+            if ( l_net )
+                l_addr = (dap_chain_addr_t *) dap_chain_wallet_get_addr(l_wallet, l_net->pub.id );
+        }
+        if(!l_addr && l_addr_str)
+            l_addr = dap_chain_addr_from_str(l_addr_str);
+
+        dap_ledger_t *l_ledger = dap_chain_ledger_by_net_name((const char *) l_net_name);
+        if(!l_net_name) {
+            dap_chain_node_cli_set_reply_text(str_reply, "wallet info requires parameter 'net'");
+            return -1;
+        }
+        else {
+            if((l_ledger = dap_chain_ledger_by_net_name(l_net_name)) == NULL) {
+                dap_chain_node_cli_set_reply_text(str_reply, "not found net by name '%s'", l_net_name);
+                return -1;
+            }
+        }
+
+        if(l_addr) {
+            char *l_addr_str = dap_chain_addr_to_str((dap_chain_addr_t*) l_addr);
+            if(l_wallet)
+                dap_string_append_printf(l_string_ret, "wallet: %s\n", l_wallet->name);
+            dap_string_append_printf(l_string_ret, "addr: %s\n", (l_addr_str) ? l_addr_str : "-");
+
+            size_t l_addr_tokens_size = 0;
+            char **l_addr_tokens = NULL;
+            //dap_chain_ledger_addr_get_token_ticker_all(l_ledger, l_addr, &l_addr_tokens, &l_addr_tokens_size);
+            // seriously?...
+            dap_chain_ledger_addr_get_token_ticker_all_fast(l_ledger, l_addr, &l_addr_tokens, &l_addr_tokens_size);
+            if(l_addr_tokens_size > 0)
+                dap_string_append_printf(l_string_ret, "balance:\n");
+            else
+                dap_string_append_printf(l_string_ret, "balance:\u00a00");
+            for(size_t i = 0; i < l_addr_tokens_size; i++) {
+                if(l_addr_tokens[i]) {
+                    uint64_t l_balance = dap_chain_ledger_calc_balance(l_ledger, l_addr, l_addr_tokens[i]);
+                    long  double l_balance_coins = (long double) l_balance / DATOSHI_LD ;
+                    //dap_string_append_printf(l_string_ret, "          %.3Lf (%llu) %s\n", l_balance_coins,
+                    dap_string_append_printf(l_string_ret, "\t\u00a0%.3Lf (%llu) %s", l_balance_coins,
+                            l_balance, l_addr_tokens[i]);
+                    if(i < l_addr_tokens_size - 1)
+                        dap_string_append_printf(l_string_ret, "\n");
+
+                }
+                DAP_DELETE(l_addr_tokens[i]);
+            }
+            DAP_DELETE(l_addr_tokens);
+            DAP_DELETE(l_addr_str);
+            if(l_wallet)
+                dap_chain_wallet_close(l_wallet);
+        }
+        else {
+            if(l_wallet)
+                dap_chain_wallet_close(l_wallet);
+            dap_string_free(l_string_ret, true);
+            dap_chain_node_cli_set_reply_text(str_reply, "wallet not found");
+            return -1;
+        }
+    }
+        break;
+    }
+
+    char *l_str_ret_tmp = dap_string_free(l_string_ret, false);
+    char *str_ret = dap_strdup(l_str_ret_tmp);
+    dap_chain_node_cli_set_reply_text(str_reply, str_ret);
+    DAP_DELETE(l_str_ret_tmp);
+    return 0;
+}
+
+/**
+ * @brief s_values_parse_net_chain
+ * @param argc
+ * @param argv
+ * @param str_reply
+ * @param l_chain
+ * @param l_net
+ * @return
+ */
+int dap_chain_node_cli_cmd_values_parse_net_chain(int *a_arg_index, int argc, char ** argv, char ** a_str_reply,
+        dap_chain_t ** a_chain, dap_chain_net_t ** a_net)
+{
+    const char * l_chain_str = NULL;
+    const char * l_net_str = NULL;
+
+    // Net name
+    if(a_net)
+        dap_chain_node_cli_find_option_val(argv, *a_arg_index, argc, "-net", &l_net_str);
+    else
+        return -100;
+
+    // Select network
+    if(!l_net_str) {
+        dap_chain_node_cli_set_reply_text(a_str_reply, "%s requires parameter '-net'", argv[0]);
+        return -101;
+    }
+
+    if((*a_net = dap_chain_net_by_name(l_net_str)) == NULL) { // Can't find such network
+        dap_chain_node_cli_set_reply_text(a_str_reply, "%s can't find network \"%s\"", argv[0], l_net_str);
+        return -102;
+    }
+
+    // Chain name
+    if(a_chain) {
+        dap_chain_node_cli_find_option_val(argv, *a_arg_index, argc, "-chain", &l_chain_str);
+
+        // Select chain
+        if(l_chain_str) {
+            if((*a_chain = dap_chain_net_get_chain_by_name(*a_net, l_chain_str)) == NULL) { // Can't find such chain
+                dap_chain_node_cli_set_reply_text(a_str_reply,
+                        "%s requires parameter '-chain' to be valid chain name in chain net %s",
+                        argv[0], l_net_str);
+                return -103;
+            }
+        }
+        else {
+            dap_chain_node_cli_set_reply_text(a_str_reply,
+                    "%s requires parameter '-chain'", argv[0]);
+            return -104;
+        }
+    }
+    return 0;
+
+}
+
+/**
+ * @brief com_token_decl_sign
+ * @param argc
+ * @param argv
+ * @param arg_func
+ * @param str_reply
+ * @return
+ */
+int com_token_decl_sign(int argc, char ** argv, void *arg_func, char ** a_str_reply)
+{
+    int arg_index = 1;
+
+    const char * l_datum_hash_str = NULL;
+    // Chain name
+    dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-datum", &l_datum_hash_str);
+
+    if(l_datum_hash_str) {
+        const char * l_certs_str = NULL;
+        dap_cert_t ** l_certs = NULL;
+        size_t l_certs_count = 0;
+        dap_chain_t * l_chain;
+
+        dap_chain_net_t * l_net = NULL;
+
+        dap_chain_node_cli_cmd_values_parse_net_chain(&arg_index, argc, argv, a_str_reply, &l_chain, &l_net);
+        if(!l_net)
+            return -1;
+        else {
+            if(*a_str_reply) {
+                DAP_DELETE(*a_str_reply);
+                *a_str_reply = NULL;
+            }
+        }
+
+        // Certificates thats will be used to sign currend datum token
+        dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-certs", &l_certs_str);
+
+        // Load certs lists
+        if (l_certs_str)
+            dap_cert_parse_str_list(l_certs_str, &l_certs, &l_certs_count);
+
+        if(!l_certs_count) {
+            dap_chain_node_cli_set_reply_text(a_str_reply,
+                    "token_sign command requres at least one valid certificate to sign the basic transaction of emission");
+            return -7;
+        }
+
+        char * l_gdb_group_mempool = dap_chain_net_get_gdb_group_mempool(l_chain);
+        if(!l_gdb_group_mempool) {
+            l_gdb_group_mempool = dap_chain_net_get_gdb_group_mempool_by_chain_type(l_net, CHAIN_TYPE_TOKEN);
+        }
+
+        log_it(L_DEBUG, "Requested to sign token declaration %s in gdb://%s with certs %s",
+                l_gdb_group_mempool, l_datum_hash_str, l_certs_str);
+
+        dap_chain_datum_t * l_datum = NULL;
+        size_t l_datum_size = 0;
+        if((l_datum = (dap_chain_datum_t*) dap_chain_global_db_gr_get(
+                l_datum_hash_str, &l_datum_size, l_gdb_group_mempool)) != NULL) {
+
+            // Check if its token declaration
+            if(l_datum->header.type_id == DAP_CHAIN_DATUM_TOKEN_DECL) {
+                dap_chain_datum_token_t * l_datum_token = (dap_chain_datum_token_t *) l_datum->data;
+                size_t l_datum_token_size = l_datum->header.data_size;
+                size_t l_signs_size = l_datum_token_size - sizeof(l_datum_token->header_private);
+
+                // Check for signatures, are they all in set and are good enought?
+                size_t l_signs_count = 0;
+
+                for(size_t l_offset = 0; l_offset < l_signs_size; l_signs_count++) {
+                    dap_sign_t * l_sign = (dap_sign_t *) l_datum_token->data + l_offset;
+                    l_offset += dap_sign_get_size(l_sign);
+                    if( dap_sign_verify(l_sign, l_datum_token, sizeof(l_datum_token->header_private)) != 1) {
+                        log_it(L_WARNING, "Wrong signature %u for datum_token with key %s in mempool!", l_signs_count, l_datum_hash_str);
+                        dap_chain_node_cli_set_reply_text(a_str_reply,
+                                "Datum %s with datum token has wrong signature %u, break process and exit",
+                                l_datum_hash_str, l_signs_count );
+                        DAP_DELETE(l_datum);
+                        //DAP_DELETE(l_datum_token);
+                        DAP_DELETE(l_gdb_group_mempool);
+                        return -666;
+                    }else{
+                        log_it(L_DEBUG,"Sign %lu passed",l_signs_count);
+                    }
+                }
+                log_it(L_DEBUG, "Datum %s with token declaration: %u signatures are verified well (sign_size = %u)",l_datum_hash_str,
+                                l_signs_count, l_signs_size);
+
+                // Check if all signs are present
+                if(l_signs_count == l_datum_token->header_private.signs_total) {
+                    dap_chain_node_cli_set_reply_text(a_str_reply,
+                            "Datum %s with datum token has all signs on board. Can't add anything in it");
+                    DAP_DELETE(l_datum);
+                    //DAP_DELETE(l_datum_token);
+                    DAP_DELETE(l_gdb_group_mempool);
+                    return -7;
+                } // Check if more signs that could be (corrupted datum)
+                else if(l_signs_count > l_datum_token->header_private.signs_total) {
+                    dap_chain_node_cli_set_reply_text(a_str_reply,
+                            "Warning! Datum %s with datum token has more signs on board (%u) than its possible to have (%u)!",
+                            l_signs_count, l_datum_token->header_private.signs_total);
+                    DAP_DELETE(l_datum);
+                    //DAP_DELETE(l_datum_token);
+                    DAP_DELETE(l_gdb_group_mempool);
+                    return -8;
+                } // Check if we have enough place to sign the datum token declaration
+                else if(l_datum_token->header_private.signs_total >= l_signs_count + l_certs_count) {
+                    size_t l_offset = 0;
+                    for(size_t i = 0; i < l_certs_count; i++) {
+                        dap_sign_t * l_sign = dap_sign_create(l_certs[i]->enc_key,
+                                l_datum_token,
+                                sizeof(l_datum_token->header_private), 0);
+                        size_t l_sign_size = dap_sign_get_size(l_sign);
+
+
+                        l_signs_size+= l_sign_size;
+                        l_datum_size += l_sign_size;
+                        l_datum_token_size+= l_sign_size;
+
+                        if ( l_datum = DAP_REALLOC(l_datum, l_datum_size) ){ // add place for new signatures
+                            l_datum_token = (dap_chain_datum_token_t*) l_datum->data;
+                            l_datum->header.data_size = l_datum_token_size;
+                            memcpy(l_datum_token->data + l_offset, l_sign, l_sign_size);
+                            log_it(L_DEBUG, "Added datum token declaration sign with cert %s (new size %lu)",
+                                   l_certs[i]->name , l_datum_size);
+                            DAP_DELETE(l_sign);
+
+                            l_offset += l_sign_size;
+                        } else{
+                            log_it(L_ERROR, "Can't allocate more memory for datum token");
+                            return -81;
+                        }
+                    }
+
+
+                    // Recalc hash, string and place new datum
+
+                    // Calc datum's hash
+                    dap_chain_hash_fast_t l_key_hash={0};
+                    dap_hash_fast(l_datum, l_datum_size, &l_key_hash);
+                    char * l_key_str = dap_chain_hash_fast_to_str_new(&l_key_hash);
+
+                    // Add datum to mempool with datum_token hash as a key
+                    if(dap_chain_global_db_gr_set(dap_strdup(l_key_str), (uint8_t *) l_datum, l_datum_size, l_gdb_group_mempool)) {
+
+                        char* l_hash_str = strdup(l_datum_hash_str);
+                        // Remove old datum from pool
+                        if( dap_chain_global_db_gr_del( dap_strdup(l_hash_str) , l_gdb_group_mempool)) {
+                            dap_chain_node_cli_set_reply_text(a_str_reply,
+                                    "datum %s produced from %s is replacing the %s in datum pool",
+                                    l_key_str, l_datum_hash_str, l_datum_hash_str);
+
+                            DAP_DELETE(l_datum);
+                            //DAP_DELETE(l_datum_token);
+                            DAP_DELETE(l_gdb_group_mempool);
+                            return 0;
+                        } else {
+                            dap_chain_node_cli_set_reply_text(a_str_reply,
+                                    "Warning! Can't remove old datum %s ( new datum %s added normaly in datum pool)",
+                                    l_datum_hash_str, l_key_str);
+                            DAP_DELETE(l_datum);
+                            //DAP_DELETE(l_datum_token);
+                            DAP_DELETE(l_gdb_group_mempool);
+                            return 1;
+                        }
+                        DAP_DELETE(l_hash_str);
+                        DAP_DELETE(l_key_str);
+                    }
+                    else {
+                        dap_chain_node_cli_set_reply_text(a_str_reply,
+                                "Error! datum %s produced from %s can't be placed in mempool",
+                                l_key_str, l_datum_hash_str);
+                        DAP_DELETE(l_datum);
+                        //DAP_DELETE(l_datum_token);
+                        DAP_DELETE(l_gdb_group_mempool);
+                        DAP_DELETE(l_key_str);
+                        return -2;
+                    }
+
+                } else {
+                    dap_chain_node_cli_set_reply_text(a_str_reply,
+                            "Error! Not enought place for new signature (%u is left when we need %u signatures)",
+                            l_datum_token->header_private.signs_total - l_signs_count, l_certs_count);
+                    return -6;
+                }
+            } else {
+                dap_chain_node_cli_set_reply_text(a_str_reply,
+                        "Error! Wrong datum type. token_decl_sign sign only token declarations datum");
+                return -61;
+            }
+        } else {
+            dap_chain_node_cli_set_reply_text(a_str_reply,
+                    "token_decl_sign can't find datum with %s hash in the mempool of %s:%s",l_datum_hash_str, l_net->pub.name,
+                    l_chain->name);
+            return -5;
+        }
+    } else {
+        dap_chain_node_cli_set_reply_text(a_str_reply, "token_decl_sign need datum <datum hash> argument");
+        return -2;
+    }
+}
+
+/**
+ * @brief com_token_decl_list
+ * @param argc
+ * @param argv
+ * @param arg_func
+ * @param str_reply
+ * @return
+ */
+int com_mempool_list(int argc, char ** argv, void *arg_func, char ** a_str_reply)
+{
+    int arg_index = 1;
+    dap_chain_t * l_chain = NULL;
+    dap_chain_net_t * l_net = NULL;
+
+    dap_chain_node_cli_cmd_values_parse_net_chain(&arg_index, argc, argv, a_str_reply, &l_chain, &l_net);
+    if(!l_net)
+        return -1;
+    else {
+        if(*a_str_reply) {
+            DAP_DELETE(*a_str_reply);
+            *a_str_reply = NULL;
+        }
+    }
+
+    if(l_net) {
+        char * l_gdb_group_mempool = NULL, *l_gdb_group_mempool_tmp;
+        if(l_chain) {
+            l_gdb_group_mempool = dap_chain_net_get_gdb_group_mempool(l_chain);
+            l_gdb_group_mempool_tmp = l_gdb_group_mempool;
+        }
+        dap_string_t * l_str_tmp = dap_string_new(NULL);
+
+        DL_FOREACH(l_net->pub.chains, l_chain) {
+            if(!l_gdb_group_mempool) {
+                l_gdb_group_mempool_tmp = dap_chain_net_get_gdb_group_mempool(l_chain);
+            }
+            size_t l_objs_size = 0;
+            dap_global_db_obj_t * l_objs = dap_chain_global_db_gr_load(l_gdb_group_mempool_tmp, &l_objs_size);
+            if(l_objs_size > 0)
+                dap_string_append_printf(l_str_tmp, "%s.%s: Found %u records :\n", l_net->pub.name, l_chain->name,
+                        l_objs_size);
+            else
+                dap_string_append_printf(l_str_tmp, "%s.%s: Not found records\n", l_net->pub.name, l_chain->name);
+            for(size_t i = 0; i < l_objs_size; i++) {
+                dap_chain_datum_t * l_datum = (dap_chain_datum_t*) l_objs[i].value;
+                char buf[50];
+                time_t l_ts_create = (time_t) l_datum->header.ts_create;
+                dap_string_append_printf(l_str_tmp, "%s: type_id=%s  data_size=%u ts_create=%s", // \n included in timestamp
+                        l_objs[i].key, c_datum_type_str[l_datum->header.type_id],
+                        l_datum->header.data_size, ctime_r(&l_ts_create, buf));
+                if ( l_datum->header.type_id == DAP_CHAIN_DATUM_TOKEN_DECL ){
+                    dap_chain_datum_token_t * l_datum_token = (dap_chain_datum_token_t *) l_datum->data;
+                    dap_string_append_printf(l_str_tmp,
+                         "\tDAP_CHAIN_DATUM_TOKEN_DECL: type=%u ticker=\"%s\" signs_total=%u signs_valid=%u\n",
+                                             l_datum_token->type, l_datum_token->ticker,
+                                             l_datum_token->header_private.signs_total, l_datum_token->header_private.signs_valid );
+                }
+            }
+            // Clean up
+            dap_chain_global_db_objs_delete(l_objs, l_objs_size);
+            if (l_gdb_group_mempool_tmp)
+                DAP_DELETE(l_gdb_group_mempool_tmp);
+            // only one time if group defined
+            if(l_gdb_group_mempool) {
+                break;
+            }
+        }
+        dap_chain_node_cli_set_reply_text(a_str_reply, l_str_tmp->str);
+        dap_string_free(l_str_tmp, false);
+
+        return 0;
+    } else {
+        dap_chain_node_cli_set_reply_text(a_str_reply,
+                "Error! Need both -net <network name> and -chain <chain name> params\n");
+        return -2;
+    }
+}
+
+/**
+ * @brief com_mempool_delete
+ * @param argc
+ * @param argv
+ * @param arg_func
+ * @param a_str_reply
+ * @return
+ */
+int com_mempool_delete(int argc, char ** argv, void *arg_func, char ** a_str_reply)
+{
+    int arg_index = 1;
+    dap_chain_t * l_chain = NULL;
+    dap_chain_net_t * l_net = NULL;
+
+    if(dap_chain_node_cli_cmd_values_parse_net_chain(&arg_index, argc, argv, a_str_reply, &l_chain, &l_net) != 0) {
+        dap_chain_node_cli_set_reply_text(a_str_reply,
+                "Error! Need both -net <network name> and -chain <chain name> params\n");
+        return -1;
+    }
+
+    if(l_chain && l_net) {  // UNUSED(l_net)
+        const char * l_datum_hash_str = NULL;
+        dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-datum", &l_datum_hash_str);
+        if(l_datum_hash_str) {
+            char * l_gdb_group_mempool = dap_chain_net_get_gdb_group_mempool(l_chain);
+            if(dap_chain_global_db_gr_del(strdup(l_datum_hash_str), l_gdb_group_mempool)) {
+                dap_chain_node_cli_set_reply_text(a_str_reply, "Datum %s deleted", l_datum_hash_str);
+                return 0;
+            } else {
+                dap_chain_node_cli_set_reply_text(a_str_reply, "Error! Can't find datum %s", l_datum_hash_str);
+                return -4;
+            }
+            DAP_DELETE(l_gdb_group_mempool);
+        } else {
+            dap_chain_node_cli_set_reply_text(a_str_reply, "Error! %s requires -datum <datum hash> option", argv[0]);
+            return -3;
+        }
+    } else {
+        dap_chain_node_cli_set_reply_text(a_str_reply,
+                "Error! Need both -net <network name> and -chain <chain name> params\n");
+        return -2;
+    }
+}
+
+/**
+ * @brief com_mempool_proc
+ * @param argc
+ * @param argv
+ * @param arg_func
+ * @param a_str_reply
+ * @return
+ */
+int com_mempool_proc(int argc, char ** argv, void *arg_func, char ** a_str_reply)
+{
+    int arg_index = 1;
+    dap_chain_t * l_chain = NULL;
+    dap_chain_net_t * l_net = NULL;
+
+    dap_chain_node_cli_cmd_values_parse_net_chain(&arg_index, argc, argv, a_str_reply, &l_chain, &l_net);
+    if(!l_net)
+        return -1;
+    else {
+        if(*a_str_reply) {
+            DAP_DELETE(*a_str_reply);
+            *a_str_reply = NULL;
+        }
+    }
+    char * l_gdb_group_mempool = NULL, *l_gdb_group_mempool_tmp;
+    if(l_chain) {
+        l_gdb_group_mempool = dap_chain_net_get_gdb_group_mempool(l_chain);
+        l_gdb_group_mempool_tmp = l_gdb_group_mempool;
+    }
+
+    dap_string_t * l_str_tmp = dap_string_new(NULL);
+    DL_FOREACH(l_net->pub.chains, l_chain) {
+        if(!l_gdb_group_mempool) {
+            l_gdb_group_mempool_tmp = dap_chain_net_get_gdb_group_mempool(l_chain);
+        }
+        size_t l_objs_size = 0;
+        dap_global_db_obj_t * l_objs = dap_chain_global_db_gr_load(l_gdb_group_mempool_tmp, &l_objs_size);
+        if(l_objs_size) {
+            dap_string_append_printf(l_str_tmp, "%s.%s: Found %u records :\n", l_net->pub.name, l_chain->name,
+                    l_objs_size);
+            size_t l_datums_size = l_objs_size;
+            dap_chain_datum_t ** l_datums = DAP_NEW_Z_SIZE(dap_chain_datum_t*,
+                    sizeof(dap_chain_datum_t*) * l_datums_size);
+            size_t l_objs_size_tmp = (l_objs_size > 15) ? min(l_objs_size, 10) : l_objs_size;
+            for(size_t i = 0; i < l_objs_size; i++) {
+                dap_chain_datum_t * l_datum = (dap_chain_datum_t*) l_objs[i].value;
+                l_datums[i] = l_datum;
+                if(i < l_objs_size_tmp) {
+                    char buf[50];
+                    time_t l_ts_create = (time_t) l_datum->header.ts_create;
+                    dap_string_append_printf(l_str_tmp, "0x%s: type_id=%s ts_create=%s data_size=%u\n",
+                            l_objs[i].key, c_datum_type_str[l_datum->header.type_id],
+                            ctime_r(&l_ts_create, buf), l_datum->header.data_size);
+                }
+            }
+            if(l_objs_size > 15) {
+                dap_string_append_printf(l_str_tmp, "...\n");
+            }
+
+            size_t l_objs_processed = 0;
+            bool l_procecced[l_objs_size];
+            for(size_t i = 0; i < l_objs_size; i++) {
+                int l_is_processed = l_chain->callback_datums_pool_proc(l_chain, l_datums + i, 1); //l_datums_size
+                l_objs_processed += l_is_processed;
+                l_procecced[i] = l_is_processed;
+            }
+            DAP_DELETE(l_datums);
+            // Delete processed objects
+            size_t l_objs_processed_tmp = (l_objs_processed > 15) ? min(l_objs_processed, 10) : l_objs_processed;
+            size_t l_objs_processed_cur = 0;
+            for(size_t i = 0; i < l_datums_size; i++) {
+                if(l_procecced[i]!=1)
+                    continue;
+                dap_chain_global_db_gr_del( dap_strdup(l_objs[i].key), l_gdb_group_mempool_tmp);
+                l_objs_processed_cur++;
+                if(l_objs_processed_cur < l_objs_processed_tmp) {
+                    dap_string_append_printf(l_str_tmp, "New event created, removed datum 0x%s from mempool \n",
+                            l_objs[i].key);
+                }
+            }
+            if(l_objs_processed > 15) {
+                dap_string_append_printf(l_str_tmp, "...\n");
+            }
+            if(l_objs_processed < l_datums_size)
+                dap_string_append_printf(l_str_tmp, "%s.%s: %d records not processed\n", l_net->pub.name, l_chain->name,
+                        l_datums_size - l_objs_processed);
+            dap_chain_global_db_objs_delete(l_objs, l_objs_size);
+        }
+        else {
+            dap_string_append_printf(l_str_tmp, "%s.%s: No records in mempool\n", l_net->pub.name, l_chain ? l_chain->name : "[no chain]");
+        }
+        DAP_DELETE(l_gdb_group_mempool);
+        // only one time if group defined
+        if(l_gdb_group_mempool)
+            break;
+    }
+    dap_chain_node_cli_set_reply_text(a_str_reply, l_str_tmp->str);
+    dap_string_free(l_str_tmp, true);
+    return 0;
+}
+
+
+/**
+ * @brief com_token_decl_update
+ * @param argc
+ * @param argv
+ * @param arg_func
+ * @param str_reply
+ * @return
+ * @details token_decl_update -net <net name> -chain <chain name> -token <token ticker> -type private -flags [<Flag 1>][,<Flag 2>]...[,<Flag N>]...  [-<Param name 1> <Param Value 1>] [-Param name 2> <Param Value 2>] ...[-<Param Name N> <Param Value N>]\n"
+ *  \t   Updatetoken for <netname>:<chain name> with ticker <token ticker>, flags <Flag 1>,<Flag2>...<Flag N>"
+ *  \t   and custom parameters list <Param 1>, <Param 2>...<Param N>."
+ *  \n"
+ *  ==Flags=="
+ *  \t ALL_BLOCKED:\t Blocked all permissions, usefull add it first and then add allows what you want to allow\n"
+ *  \t ALL_ALLOWED:\t Allowed all permissions if not blocked them. Be careful with this mode\n"
+ *  \t ALL_FROZEN:\t All permissions are temprorary frozen\n"
+ *  \t ALL_UNFROZEN:\t Unfrozen permissions\n"
+ *  \t STATIC_ALL:\t No token manipulations after declarations at all. Token declares staticly and can't variabed after\n"
+ *  \t STATIC_FLAGS:\t No token manipulations after declarations with flags\n"
+ *  \t STATIC_PERMISSIONS_ALL:\t No all permissions lists manipulations after declarations\n"
+ *  \t STATIC_PERMISSIONS_DATUM_TYPE:\t No datum type permissions lists manipulations after declarations\n"
+ *  \t STATIC_PERMISSIONS_TX_SENDER:\t No tx sender permissions lists manipulations after declarations\n"
+ *  \t STATIC_PERMISSIONS_TX_RECEIVER:\t No tx receiver permissions lists manipulations after declarations\n"
+    "\n"
+    "==Params==\n"
+    "General:\n"
+    "\t -flags_set <value>:\t Set list of flags from <value> to token declaration\n"
+    "\t -flags_unset <value>:\t Unset list of flags from <value> from token declaration\n"
+    "\t -total_supply <value>:\t Set total supply - emission's maximum - to the <value>\n"
+    "\t -total_signs_valid <value>:\t Set valid signatures count's minimum\n"
+    "\t -total_signs_add <value>:\t Add signature's pkey fingerprint to the list of owners\n"
+    "\t -total_signs_remove <value>:\t Remove signature's pkey fingerprint from the owners\n"
+    "\nDatum type allowed/blocked updates:\n"
+    "\t -datum_type_allowed_add <value>:\t Add allowed datum type(s)\n"
+    "\t -datum_type_allowed_remove <value>:\t Remove datum type(s) from allowed\n"
+    "\t -datum_type_allowed_clear:\t Remove all datum types from allowed\n"
+    "\t -datum_type_blocked_add <value>:\t Add blocked datum type(s)\n"
+    "\t -datum_type_blocked_remove <value>:\t Remove datum type(s) from blocked\n"
+    "\t -datum_type_blocked_clear:\t Remove all datum types from blocked\n"
+    "\nTx receiver addresses allowed/blocked updates:\n"
+    "\t -tx_receiver_allowed_add <value>:\t Add allowed tx receiver(s)\n"
+    "\t -tx_receiver_allowed_remove <value>:\t Remove tx receiver(s) from allowed\n"
+    "\t -tx_receiver_allowed_clear:\t Remove all tx receivers from allowed\n"
+    "\t -tx_receiver_blocked_add <value>:\t Add blocked tx receiver(s)\n"
+    "\t -tx_receiver_blocked_remove <value>:\t Remove tx receiver(s) from blocked\n"
+    "\t -tx_receiver_blocked_clear:\t Remove all tx receivers from blocked\n"
+    "\n Tx sender addresses allowed/blocked updates:\n"
+    "\t -tx_sender_allowed_add <value>:\t Add allowed tx sender(s)\n"
+    "\t -tx_sender_allowed_remove <value>:\t Remove tx sender(s) from allowed\n"
+    "\t -tx_sender_allowed_clear:\t Remove all tx senders from allowed\n"
+    "\t -tx_sender_blocked_add <value>:\t Add allowed tx sender(s)\n"
+    "\t -tx_sender_blocked_remove <value>:\t Remove tx sender(s) from blocked\n"
+    "\t -tx_sender_blocked_clear:\t Remove all tx sender(s) from blocked\n"
+    "\n"
+ */
+int com_token_decl_update(int argc, char ** argv, void *arg_func, char ** a_str_reply)
+{
+    return -1;
+}
+
+/**
+ * @brief com_token_decl
+ * @param argc
+ * @param argv
+ * @param arg_func
+ * @param str_reply
+ * @return
+ * @details token_decl -net <net name> -chain <chain name> -token <token ticker> -total_supply <total supply> -signs_total <sign total> -signs_emission <signs for emission> -certs <certs list>\n"
+ *  \t Declare new simple token for <netname>:<chain name> with ticker <token ticker>, maximum emission <total supply> and <signs for emission> from <signs total> signatures on valid emission\n"
+ *  \t   Extended private token declaration\n"
+ *  \t token_decl -net <net name> -chain <chain name> -token <token ticker> -type private -flags [<Flag 1>][,<Flag 2>]...[,<Flag N>]...  [-<Param name 1> <Param Value 1>] [-Param name 2> <Param Value 2>] ...[-<Param Name N> <Param Value N>]\n"
+ *  \t   Declare new token for <netname>:<chain name> with ticker <token ticker>, flags <Flag 1>,<Flag2>...<Flag N>"
+ *  \t   and custom parameters list <Param 1>, <Param 2>...<Param N>."
+ *  \n"
+ *  ==Flags=="
+ *  \t ALL_BLOCKED:\t Blocked all permissions, usefull add it first and then add allows what you want to allow\n"
+ *  \t ALL_ALLOWED:\t Allowed all permissions if not blocked them. Be careful with this mode\n"
+ *  \t ALL_FROZEN:\t All permissions are temprorary frozen\n"
+ *  \t ALL_UNFROZEN:\t Unfrozen permissions\n"
+ *  \t STATIC_ALL:\t No token manipulations after declarations at all. Token declares staticly and can't variabed after\n"
+ *  \t STATIC_FLAGS:\t No token manipulations after declarations with flags\n"
+ *  \t STATIC_PERMISSIONS_ALL:\t No all permissions lists manipulations after declarations\n"
+ *  \t STATIC_PERMISSIONS_DATUM_TYPE:\t No datum type permissions lists manipulations after declarations\n"
+ *  \t STATIC_PERMISSIONS_TX_SENDER:\t No tx sender permissions lists manipulations after declarations\n"
+ *  \t STATIC_PERMISSIONS_TX_RECEIVER:\t No tx receiver permissions lists manipulations after declarations\n"
+    "\n"
+    "==Params==\n"
+    "General:\n"
+    "\t -flags <value>:\t Set list of flags from <value> to token declaration\n"
+    "\t -total_supply <value>:\t Set total supply - emission's maximum - to the <value>\n"
+    "\t -signs_valid <value>:\t Set valid signatures count's minimum\n"
+    "\t -signs <value>:\t Add signature's pkey fingerprint to the list of owners\n"
+    "\nDatum type allowed/blocked:\n"
+    "\t -datum_type_allowed <value>:\t Allowed datum type(s)\n"
+    "\t -datum_type_blocked <value>:\t Blocked datum type(s)\n"
+    "\nTx receiver addresses allowed/blocked:\n"
+    "\t -tx_receiver_allowed <value>:\t Allowed tx receiver(s)\n"
+    "\t -tx_receiver_blocked <value>:\t Blocked tx receiver(s)\n"
+    "\n Tx sender addresses allowed/blocked:\n"
+    "\t -tx_sender_allowed <value>:\t Allowed tx sender(s)\n"
+    "\t -tx_sender_blocked <value>:\t Blocked tx sender(s)\n"
+    "\n"
+ */
+int com_token_decl(int argc, char ** argv, void *arg_func, char ** a_str_reply)
+{
+    int l_arg_index = 1;
+
+    const char * l_type_str = NULL;
+    uint16_t l_type = DAP_CHAIN_DATUM_TOKEN_PRIVATE;
+
+    const char * l_ticker = NULL;
+
+    const char * l_total_supply_str = NULL;
+    uint64_t l_total_supply = 0;
+
+    const char * l_signs_emission_str = NULL;
+    uint16_t l_signs_emission = 0;
+
+    const char * l_signs_total_str = NULL;
+    uint16_t l_signs_total = 0;
+
+    const char * l_certs_str = NULL;
+
+    dap_cert_t ** l_certs = NULL;
+    size_t l_certs_size = 0;
+
+    dap_chain_t * l_chain = NULL;
+    dap_chain_net_t * l_net = NULL;
+
+    dap_chain_node_cli_cmd_values_parse_net_chain(&l_arg_index, argc, argv, a_str_reply, &l_chain, &l_net);
+    if(!l_net)
+        return -1;
+    else {
+        if(*a_str_reply) {
+            DAP_DELETE(*a_str_reply);
+            *a_str_reply = NULL;
+        }
+    }
+    // Token ticker
+    l_arg_index=dap_chain_node_cli_find_option_val(argv, l_arg_index, argc, "-token", &l_ticker);
+    // Check for ticker
+    if(!l_ticker) {
+        dap_chain_node_cli_set_reply_text(a_str_reply, "token_decl requires parameter 'token'");
+        return -2;
+    }
+
+    // Token type
+    l_arg_index=dap_chain_node_cli_find_option_val(argv, l_arg_index, argc, "-type", &l_type_str);
+
+    if (strcmp( l_type_str, "private") == 0){
+        l_type = DAP_CHAIN_DATUM_TOKEN_PRIVATE_DECL;
+    }else if (strcmp( l_type_str, "private_simple") == 0){
+        l_type = DAP_CHAIN_DATUM_TOKEN_PRIVATE;
+    }else if (strcmp( l_type_str, "public_simple") == 0){
+        l_type = DAP_CHAIN_DATUM_TOKEN_PUBLIC;
+    }
+
+    dap_chain_datum_token_t * l_datum_token = NULL;
+    size_t l_signs_offset = 0;
+
+    switch(l_type){
+        case DAP_CHAIN_DATUM_TOKEN_PRIVATE_DECL:{
+            dap_list_t *l_tsd_list = dap_list_alloc();
+            size_t l_tsd_size = 0;
+            uint16_t l_flags = 0;
+            while (l_arg_index<argc){
+                if ( strcmp( argv[l_arg_index],"-flags" )){
+                }else if ( strcmp( argv[l_arg_index],"-total_supply" )){
+
+                }else if ( strcmp( argv[l_arg_index],"-signs_valid" )){
+
+                }else if ( strcmp( argv[l_arg_index],"-signs" )){
+
+                }else if ( strcmp( argv[l_arg_index],"-datum_type_allowed" )){
+
+                }else if ( strcmp( argv[l_arg_index],"-datum_type_blocked" )){
+
+                }else if ( strcmp( argv[l_arg_index],"-tx_receiver_allowed" )){
+
+                }else if ( strcmp( argv[l_arg_index],"-tx_receiver_blocked" )){
+
+                }else if ( strcmp( argv[l_arg_index],"-tx_sender_allowed" )){
+
+                }else if ( strcmp( argv[l_arg_index],"-tx_sender_blocked" )){
+
+                }else {
+
+                }
+                ++l_arg_index;
+            }
+        }break;
+        case DAP_CHAIN_DATUM_TOKEN_PRIVATE:{
+            // Total supply value
+            dap_chain_node_cli_find_option_val(argv, l_arg_index, argc, "-total_supply", &l_total_supply_str);
+
+
+            // Certificates thats will be used to sign currend datum token
+            dap_chain_node_cli_find_option_val(argv, l_arg_index, argc, "-certs", &l_certs_str);
+
+            // Signs number thats own emissioncan't find
+            dap_chain_node_cli_find_option_val(argv, l_arg_index, argc, "-signs_total", &l_signs_total_str);
+
+            // Signs minimum number thats need to authorize the emission
+            dap_chain_node_cli_find_option_val(argv, l_arg_index, argc, "-signs_emission", &l_signs_emission_str);
+
+
+            if(!l_total_supply_str) {
+                dap_chain_node_cli_set_reply_text(a_str_reply, "token_create requires parameter '-total_supply'");
+                return -3;
+            } else {
+                char * l_tmp = NULL;
+                if((l_total_supply = strtoull(l_total_supply_str, &l_tmp, 10)) == 0) {
+                    dap_chain_node_cli_set_reply_text(a_str_reply,
+                            "token_create requires parameter '-total_supply' to be unsigned integer value that fits in 8 bytes");
+                    return -4;
+                }
+            }
+
+
+
+            // Signs emission
+            if(!l_signs_emission_str) {
+                dap_chain_node_cli_set_reply_text(a_str_reply, "token_create requires parameter '-signs_emission'");
+                return -5;
+            } else {
+                char * l_tmp = NULL;
+                if((l_signs_emission = (uint16_t) strtol(l_signs_emission_str, &l_tmp, 10)) == 0) {
+                    dap_chain_node_cli_set_reply_text(a_str_reply,
+                            "token_create requires parameter 'signs_emission' to be unsigned integer value that fits in 2 bytes");
+                    return -6;
+                }
+            }
+
+            // Signs total
+            if(!l_signs_total_str) {
+                dap_chain_node_cli_set_reply_text(a_str_reply, "token_decl requires parameter 'signs_total'");
+                return -7;
+            } else {
+                char * l_tmp = NULL;
+                if((l_signs_total = (uint16_t) strtol(l_signs_total_str, &l_tmp, 10)) == 0) {
+                    dap_chain_node_cli_set_reply_text(a_str_reply,
+                            "token_create requires parameter 'signs_total' to be unsigned integer value that fits in 2 bytes");
+                    return -8;
+                }
+            }
+
+            // Check certs list
+            if(!l_certs_str) {
+                dap_chain_node_cli_set_reply_text(a_str_reply, "token_decl requires parameter 'certs'");
+                return -9;
+            }
+
+            // Load certs lists
+            dap_cert_parse_str_list(l_certs_str, &l_certs, &l_certs_size);
+            if(!l_certs_size) {
+                dap_chain_node_cli_set_reply_text(a_str_reply,
+                        "token_decl command requres at least one valid certificate to sign the basic transaction of emission");
+                return -10;
+            }
+
+            // If we have more certs than we need signs - use only first part of the list
+            if(l_certs_size > l_signs_total)
+                l_certs_size = l_signs_total;
+
+            // Create new datum token
+            l_datum_token = DAP_NEW_Z_SIZE(dap_chain_datum_token_t, sizeof(dap_chain_datum_token_t));
+            l_datum_token->type = DAP_CHAIN_DATUM_TOKEN_PRIVATE;
+            dap_snprintf(l_datum_token->ticker, sizeof(l_datum_token->ticker), "%s", l_ticker);
+            l_datum_token->header_private.total_supply = l_total_supply;
+            l_datum_token->header_private.signs_total = l_signs_total;
+            l_datum_token->header_private.signs_valid = l_signs_emission;
+
+            // Sign header with all certificates in the list and add signs to the end of ticker declaration
+            // Important:
+            for(size_t i = 0; i < l_certs_size; i++) {
+                dap_sign_t * l_sign = dap_cert_sign(l_certs[i],
+                        l_datum_token,
+                        sizeof(l_datum_token->header_private),
+                        0);
+                size_t l_sign_size = dap_sign_get_size(l_sign);
+                l_datum_token = DAP_REALLOC(l_datum_token, sizeof(dap_chain_datum_token_t) + l_signs_offset + l_sign_size);
+                memcpy(l_datum_token->data + l_signs_offset, l_sign, l_sign_size);
+                l_signs_offset += l_sign_size;
+                DAP_DELETE(l_sign);
+            }
+        }break;
+        default:
+            dap_chain_node_cli_set_reply_text(a_str_reply,
+                    "Unknown token type");
+            return -8;
+    }
+
+    dap_chain_datum_t * l_datum = dap_chain_datum_create(DAP_CHAIN_DATUM_TOKEN_DECL, l_datum_token,
+            sizeof(l_datum_token->header_private) + l_signs_offset);
+    size_t l_datum_size = dap_chain_datum_size(l_datum);
+
+    // Calc datum's hash
+    dap_chain_hash_fast_t l_key_hash;
+    dap_hash_fast(l_datum, l_datum_size, &l_key_hash);
+    char * l_key_str = dap_chain_hash_fast_to_str_new(&l_key_hash);
+
+    // Add datum to mempool with datum_token hash as a key
+    char * l_gdb_group_mempool;
+    if(l_chain) {
+        l_gdb_group_mempool = dap_chain_net_get_gdb_group_mempool(l_chain);
+    }
+    else {
+        l_gdb_group_mempool = dap_chain_net_get_gdb_group_mempool_by_chain_type(l_net, CHAIN_TYPE_TOKEN);
+
+    }
+    if(dap_chain_global_db_gr_set(dap_strdup(l_key_str), (uint8_t *) l_datum, l_datum_size, l_gdb_group_mempool)) {
+        dap_chain_node_cli_set_reply_text(a_str_reply, "datum %s with token %s is placed in datum pool ", l_key_str,
+                l_ticker);
+        DAP_DELETE(l_datum);
+        DAP_DELETE(l_datum_token);
+        DAP_DELETE(l_gdb_group_mempool);
+        DAP_DELETE(l_key_str);
+        return 0;
+    }
+    else {
+        dap_chain_node_cli_set_reply_text(a_str_reply, "datum tx %s is not placed in datum pool ", l_key_str);
+        DAP_DELETE(l_datum);
+        DAP_DELETE(l_datum_token);
+        DAP_DELETE(l_gdb_group_mempool);
+        DAP_DELETE(l_key_str);
+        return -2;
+    }
+
+}
+
+/**
+ * @brief com_token_emit
+ * @param argc
+ * @param argv
+ * @param arg_func
+ * @param str_reply
+ * @return
+ */
+int com_token_emit(int argc, char ** argv, void *arg_func, char ** str_reply)
+{
+    int arg_index = 1;
+    const char *str_tmp = NULL;
+    char *str_reply_tmp = NULL;
+    uint64_t l_emission_value = 0;
+
+    const char * l_ticker = NULL;
+
+    const char * l_addr_str = NULL;
+
+    const char * l_certs_str = NULL;
+
+    dap_cert_t ** l_certs = NULL;
+    size_t l_certs_size = 0;
+
+    const char * l_chain_emission_str = NULL;
+    dap_chain_t * l_chain_emission = NULL;
+
+    const char * l_chain_base_tx_str = NULL;
+    dap_chain_t * l_chain_base_tx = NULL;
+
+    const char * l_net_str = NULL;
+    dap_chain_net_t * l_net = NULL;
+
+    // Wallet address that recieves the emission
+    dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-certs", &l_certs_str);
+
+    // Wallet address that recieves the emission
+    dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-addr", &l_addr_str);
+
+    // Token ticker
+    dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-token", &l_ticker);
+
+    // Token emission
+    if(dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-emission_value", &str_tmp)) {
+        l_emission_value = strtoull(str_tmp, NULL, 10);
+    }
+
+    if(!l_emission_value) {
+        dap_chain_node_cli_set_reply_text(str_reply, "token_emit requires parameter '-emission_value'");
+        return -1;
+    }
+
+    if(!l_addr_str) {
+        dap_chain_node_cli_set_reply_text(str_reply, "token_emit requires parameter '-addr'");
+        return -2;
+    }
+
+    if(!l_ticker) {
+        dap_chain_node_cli_set_reply_text(str_reply, "token_emit requires parameter '-token'");
+        return -3;
+    }
+
+    if(!l_certs_str) {
+        dap_chain_node_cli_set_reply_text(str_reply, "token_emit requires parameter '-certs'");
+        return -4;
+    }
+
+    // Load certs
+    dap_cert_parse_str_list(l_certs_str, &l_certs, &l_certs_size);
+
+    if(!l_certs_size) {
+        dap_chain_node_cli_set_reply_text(str_reply,
+                "token_emit command requres at least one valid certificate to sign the basic transaction of emission");
+        return -5;
+    }
+
+    dap_chain_addr_t * l_addr = dap_chain_addr_from_str(l_addr_str);
+
+    if(!l_addr) {
+        dap_chain_node_cli_set_reply_text(str_reply, "address \"%s\" is invalid", l_addr_str);
+        return -4;
+    }
+
+    // Net addr
+    dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-net", &l_net_str);
+
+    // Select chain network
+    if(!l_net_str) {
+        dap_chain_node_cli_set_reply_text(str_reply, "token_create requires parameter 'net'");
+        DAP_DELETE(l_addr);
+        return -42;
+    } else {
+        if((l_net = dap_chain_net_by_name(l_net_str)) == NULL) { // Can't find such network
+            dap_chain_node_cli_set_reply_text(str_reply,
+                    "token_create requires parameter '-net' to be valid chain network name");
+            DAP_DELETE(l_addr);
+            return -43;
+        }
+    }
+
+    dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-chain_emission", &l_chain_emission_str);
+    dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-chain_base_tx", &l_chain_base_tx_str);
+
+    // Select chain emission
+    /*if(!l_chain_emission_str) {
+     dap_chain_node_cli_set_reply_text(str_reply, "token_create requires parameter '-chain_emission'");
+     return -44;
+     } */
+    if(l_chain_emission_str) {
+        if((l_chain_emission = dap_chain_net_get_chain_by_name(l_net, l_chain_emission_str)) == NULL) { // Can't find such chain
+            dap_chain_node_cli_set_reply_text(str_reply,
+                    "token_create requires parameter '-chain_emission' to be valid chain name in chain net %s",
+                    l_net_str);
+            DAP_DELETE(l_addr);
+            return -45;
+        }
+    }
+
+    // Select chain emission
+    /*if(!l_chain_base_tx_str) {
+     dap_chain_node_cli_set_reply_text(str_reply, "token_create requires parameter 'chain_base_tx'");
+     return -46;
+     }*/
+    if(l_chain_base_tx_str) {
+        if((l_chain_base_tx = dap_chain_net_get_chain_by_name(l_net, l_chain_base_tx_str)) == NULL) { // Can't find such chain
+            dap_chain_node_cli_set_reply_text(str_reply,
+                    "token_create requires parameter '-chain_emission' to be valid chain name in chain net %s",
+                    l_net_str);
+            DAP_DELETE(l_addr);
+            return -47;
+        }
+    }
+
+    // Get groups for the chains
+    char * l_gdb_group_mempool_emission, *l_gdb_group_mempool_base_tx;
+    if(l_chain_emission) {
+        l_gdb_group_mempool_emission = dap_chain_net_get_gdb_group_mempool(l_chain_emission);
+    }
+    else {
+        l_gdb_group_mempool_emission = dap_chain_net_get_gdb_group_mempool_by_chain_type(l_net, CHAIN_TYPE_EMISSION);
+    }
+    if(l_chain_base_tx) {
+        l_gdb_group_mempool_base_tx = dap_chain_net_get_gdb_group_mempool(l_chain_base_tx);
+    }
+    else {
+        l_gdb_group_mempool_base_tx = dap_chain_net_get_gdb_group_mempool_by_chain_type(l_net, CHAIN_TYPE_TX);
+    }
+    //char * l_gdb_group_mempool_emission = dap_chain_net_get_gdb_group_mempool(l_chain_emission);
+    //char * l_gdb_group_mempool_base_tx = dap_chain_net_get_gdb_group_mempool(l_chain_base_tx);
+
+    // Create emission datum
+    // then create datum in memory
+    dap_chain_datum_token_emission_t * l_token_emission;
+    size_t l_token_emission_size = sizeof(l_token_emission->hdr) +
+            sizeof(l_token_emission->data.type_auth.signs_count);
+
+    l_token_emission = DAP_NEW_Z_SIZE(dap_chain_datum_token_emission_t, l_token_emission_size);
+    strncpy(l_token_emission->hdr.ticker, l_ticker, sizeof(l_token_emission->hdr.ticker));
+    l_token_emission->hdr.value = l_emission_value;
+    l_token_emission->hdr.type = DAP_CHAIN_DATUM_TOKEN_EMISSION_TYPE_AUTH;
+    memcpy(&l_token_emission->hdr.address, l_addr, sizeof(l_token_emission->hdr.address));
+    // Then add signs
+    size_t l_offset = 0;
+    for(size_t i = 0; i < l_certs_size; i++) {
+        dap_sign_t * l_sign = dap_cert_sign(l_certs[i], &l_token_emission->hdr,
+                sizeof(l_token_emission->hdr), 0);
+        size_t l_sign_size = dap_sign_get_size(l_sign);
+        l_token_emission_size += l_sign_size;
+        l_token_emission = DAP_REALLOC(l_token_emission, l_token_emission_size);
+        memcpy(l_token_emission->data.type_auth.signs + l_offset, l_sign, l_sign_size);
+        l_offset += l_sign_size;
+        DAP_DELETE(l_sign);
+    }
+
+    // Produce datum
+    dap_chain_datum_t * l_datum_emission = dap_chain_datum_create(DAP_CHAIN_DATUM_TOKEN_EMISSION,
+            l_token_emission,
+            l_token_emission_size);
+    size_t l_datum_emission_size = sizeof(l_datum_emission->header) + l_datum_emission->header.data_size;
+
+    // Calc token's hash
+    dap_chain_hash_fast_t l_token_emission_hash;
+    dap_hash_fast(l_token_emission, l_token_emission_size, &l_token_emission_hash);
+    char * l_key_str = dap_chain_hash_fast_to_str_new(&l_token_emission_hash);
+
+    // Delete token emission
+    DAP_DELETE(l_token_emission);
+
+//    // Calc datum's hash
+//    dap_chain_hash_fast_t l_datum_emission_hash;
+//    dap_hash_fast(l_datum_emission, l_datum_emission_size, (uint8_t*) &l_datum_emission_hash);
+//    char * l_key_str = dap_chain_hash_fast_to_str_new(&l_datum_emission_hash);
+
+    // Add to mempool emission token
+    if(dap_chain_global_db_gr_set(dap_strdup(l_key_str), (uint8_t *) l_datum_emission, l_datum_emission_size
+            , l_gdb_group_mempool_emission)) {
+        str_reply_tmp = dap_strdup_printf("datum emission %s is placed in datum pool ", l_key_str);
+        DAP_DELETE(l_key_str);
+    }
+    else {
+        dap_chain_node_cli_set_reply_text(str_reply, "datum emission %s is not placed in datum pool ", l_key_str);
+        DAP_DELETE(l_key_str);
+        DAP_DELETE(l_datum_emission);
+        return -1;
+    }
+
+
+    // create first transaction (with tx_token)
+    dap_chain_datum_tx_t *l_tx = DAP_NEW_Z_SIZE(dap_chain_datum_tx_t, sizeof(dap_chain_datum_tx_t));
+    dap_chain_hash_fast_t l_tx_prev_hash = { 0 };
+    // create items
+    dap_chain_tx_token_t *l_tx_token = dap_chain_datum_tx_item_token_create(&l_token_emission_hash, l_ticker);
+    dap_chain_tx_in_t *l_in = dap_chain_datum_tx_item_in_create(&l_tx_prev_hash, 0);
+    dap_chain_tx_out_t *l_out = dap_chain_datum_tx_item_out_create(l_addr, l_emission_value);
+
+    // pack items to transaction
+    dap_chain_datum_tx_add_item(&l_tx, (const uint8_t*) l_tx_token);
+    dap_chain_datum_tx_add_item(&l_tx, (const uint8_t*) l_in);
+    dap_chain_datum_tx_add_item(&l_tx, (const uint8_t*) l_out);
+
+    // Sign all that we have with certs
+    for(size_t i = 0; i < l_certs_size; i++) {
+        if(dap_chain_datum_tx_add_sign_item(&l_tx, l_certs[i]->enc_key) < 0) {
+            dap_chain_node_cli_set_reply_text(str_reply, "No private key for certificate=%s",
+                    l_certs[i]->name);
+            DAP_DELETE(l_addr);
+            return -3;
+        }
+    }
+
+    DAP_DELETE(l_certs);
+    DAP_DELETE(l_tx_token);
+    DAP_DELETE(l_in);
+    DAP_DELETE(l_out);
+
+    size_t l_tx_size = dap_chain_datum_tx_get_size(l_tx);
+
+    // Pack transaction into the datum
+    dap_chain_datum_t * l_datum_tx = dap_chain_datum_create(DAP_CHAIN_DATUM_TX, l_tx, l_tx_size);
+    size_t l_datum_tx_size = dap_chain_datum_size(l_datum_tx);
+
+    // use l_tx hash for compatible with utho hash
+    //dap_hash_fast(l_tx, l_tx_size, &l_key_hash); //dap_hash_fast(l_datum_tx, l_datum_tx_size, &l_key_hash);
+    // calc datum hash
+    dap_chain_hash_fast_t l_datum_tx_hash;
+    dap_hash_fast(l_datum_tx, l_datum_tx_size,  &l_datum_tx_hash);
+    l_key_str = dap_chain_hash_fast_to_str_new(&l_datum_tx_hash);
+    DAP_DELETE(l_tx);
+
+    // Add to mempool tx token
+    if(dap_chain_global_db_gr_set(dap_strdup(l_key_str), l_datum_tx, l_datum_tx_size
+            , l_gdb_group_mempool_base_tx)) {
+        dap_chain_node_cli_set_reply_text(str_reply, "%s\ndatum tx %s is placed in datum pool ", str_reply_tmp,
+                l_key_str);
+        DAP_DELETE(l_key_str);
+    } else {
+        dap_chain_node_cli_set_reply_text(str_reply, "%s\ndatum tx %s is not placed in datum pool ", str_reply_tmp,
+                l_key_str);
+        DAP_DELETE(l_key_str);
+
+        return -2;
+    }
+    DAP_DELETE(str_reply_tmp);
+    DAP_DELETE(l_datum_tx);
+    DAP_DELETE(l_addr);
+    return 0;
+}
+
+/**
+ * com_tx_cond_create command
+ *
+ * Create transaction
+ */
+int com_tx_cond_create(int argc, char ** argv, void *arg_func, char **str_reply)
+{
+    (void) argc;
+    int arg_index = 1;
+    const char *c_wallets_path = dap_chain_wallet_get_path(g_config);
+    const char * l_token_ticker = NULL;
+    const char * l_wallet_from_str = NULL;
+    const char * l_wallet_to_str = NULL; //l_addr_to_str
+    const char * l_value_datoshi_str = NULL;
+    const char * l_net_name = NULL;
+    const char * l_unit_str = NULL;
+    const char * l_service_str = NULL;
+    uint64_t l_value_datoshi = 0;
+
+    // Token ticker
+    dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-token", &l_token_ticker);
+    // Wallet name - from
+    dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-wallet_f", &l_wallet_from_str);
+    // Wallet address - to
+    dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-wallet_t", &l_wallet_to_str);
+    // value datoshi
+    dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-value", &l_value_datoshi_str);
+    // net
+    dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-net", &l_net_name);
+    // unit
+    dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-unit", &l_unit_str);
+    // service
+    dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-service", &l_service_str);
+
+    if(!l_token_ticker) {
+        dap_chain_node_cli_set_reply_text(str_reply, "tx_cond_create requires parameter '-token'");
+        return -1;
+    }
+    if(!l_wallet_from_str) {
+        dap_chain_node_cli_set_reply_text(str_reply, "tx_cond_create requires parameter '-wallet_f'");
+        return -2;
+    }
+    if(!l_wallet_to_str) {
+        dap_chain_node_cli_set_reply_text(str_reply, "tx_cond_create requires parameter '-wallet_t'");
+        return -3;
+    }
+    if(!l_value_datoshi_str) {
+        dap_chain_node_cli_set_reply_text(str_reply, "tx_cond_create requires parameter '-value'");
+        return -4;
+    }
+
+    if(!l_net_name) {
+        dap_chain_node_cli_set_reply_text(str_reply, "tx_cond_create requires parameter '-net'");
+        return -5;
+    }
+    if(!l_unit_str) {
+        dap_chain_node_cli_set_reply_text(str_reply, "tx_cond_create requires parameter '-unit={mb|kb|b|sec|day}'");
+        return -6;
+    }
+    if(!l_service_str) {
+        dap_chain_node_cli_set_reply_text(str_reply, "tx_cond_create requires parameter '-service={vpn}'");
+        return -7;
+    }
+    dap_chain_net_srv_uid_t l_srv_uid = { 0 };
+    if(!dap_strcmp(l_service_str, "vpn"))
+        l_srv_uid.uint64 = 0x0000000000000001;
+    //dap_chain_addr_t *addr_to = dap_chain_addr_from_str(l_addr_to_str);
+    if(!l_srv_uid.uint64) {
+        dap_chain_node_cli_set_reply_text(str_reply, "can't recognize service='%s' unit must look like {vpn}",
+                l_service_str);
+        return -8;
+    }
+
+    dap_chain_net_srv_price_unit_uid_t l_price_unit = { .enm = SERV_UNIT_UNDEFINED };
+    if(!dap_strcmp(l_unit_str, "mb"))
+        l_price_unit.enm = SERV_UNIT_MB;
+    else if(!dap_strcmp(l_unit_str, "sec"))
+        l_price_unit.enm = SERV_UNIT_SEC;
+    else if(!dap_strcmp(l_unit_str, "day"))
+        l_price_unit.enm = SERV_UNIT_DAY;
+    else if(!dap_strcmp(l_unit_str, "kb"))
+        l_price_unit.enm = SERV_UNIT_KB;
+    else if(!dap_strcmp(l_unit_str, "b"))
+        l_price_unit.enm = SERV_UNIT_B;
+
+    if(l_price_unit.enm == SERV_UNIT_UNDEFINED) {
+        dap_chain_node_cli_set_reply_text(str_reply, "can't recognize unit='%s' unit must look like {mb|kb|b|sec|day}",
+                l_unit_str);
+        return -9;
+    }
+
+    l_value_datoshi = strtoll(l_value_datoshi_str, NULL, 10);
+    if(!l_value_datoshi) {
+        dap_chain_node_cli_set_reply_text(str_reply, "can't recognize value='%s' as a number", l_value_datoshi_str);
+        return -10;
+    }
+
+    dap_chain_net_t * l_net = l_net_name ? dap_chain_net_by_name(l_net_name) : NULL;
+    if(!l_net) {
+        dap_chain_node_cli_set_reply_text(str_reply, "can't find net '%s'", l_net_name);
+        return -11;
+    }
+    dap_chain_wallet_t *l_wallet_from = dap_chain_wallet_open(l_wallet_from_str, c_wallets_path);
+    if(!l_wallet_from) {
+        dap_chain_node_cli_set_reply_text(str_reply, "can't open wallet '%s'", l_wallet_from);
+        return -12;
+    }
+    dap_chain_wallet_t *l_wallet_cond = dap_chain_wallet_open(l_wallet_to_str, c_wallets_path);
+    if(!l_wallet_to_str) {
+        dap_chain_wallet_close(l_wallet_from);
+        dap_chain_node_cli_set_reply_text(str_reply, "can't open wallet '%s'", l_wallet_to_str);
+        return -13;
+    }
+    dap_enc_key_t *l_key_from = dap_chain_wallet_get_key(l_wallet_from, 0);
+    dap_enc_key_t *l_key_cond = dap_chain_wallet_get_key(l_wallet_cond, 0);
+
+
+    // where to take coins for service
+    const dap_chain_addr_t *l_addr_from = dap_chain_wallet_get_addr(l_wallet_from, l_net->pub.id);
+    // who will be use service, usually the same address (addr_from)
+    //const dap_chain_addr_t *l_addr_cond = dap_chain_wallet_get_addr(l_wallet_cond, l_net->pub.id);
+
+
+/*    //dap_chain_net_srv_abstract_t l_cond;
+//    dap_chain_net_srv_abstract_set(&l_cond, SERV_CLASS_PERMANENT, SERV_ID_VPN, l_value, SERV_UNIT_MB,
+//            "test vpn service");
+//    dap_ledger_t *l_ledger = dap_chain_ledger_by_net_name((const char *) c_net_name);
+
+    int res = dap_chain_mempool_tx_create_cond(NULL, l_key, l_key_cond, addr_from,
+            addr_cond,
+            NULL, l_token_ticker, l_value, 0, (const void*) &l_cond, sizeof(dap_chain_net_srv_abstract_t));
+*/
+
+    dap_chain_hash_fast_t *l_tx_cond_hash = dap_chain_mempool_tx_create_cond(l_net, l_key_from, l_key_cond, l_addr_from, l_token_ticker,
+            l_value_datoshi, 0, l_price_unit, l_srv_uid, 0, NULL, 0);
+
+    dap_chain_wallet_close(l_wallet_from);
+    dap_chain_wallet_close(l_wallet_cond);
+
+    char *l_hash_str = l_tx_cond_hash ? dap_chain_hash_fast_to_str_new(l_tx_cond_hash) : NULL;
+
+    /*dap_chain_node_cli_set_reply_text(str_reply, "cond create=%s\n",
+            (res == 0) ? "Ok" : (res == -2) ? "False, not enough funds for service fee" : "False");
+    return res;*/
+
+    int l_ret;
+    // example: cond create succefully hash=0x4AA303EB7C10430C0AAC42F399D265BC7DD09E3983E088E02B8CED38DA22EDA9
+    if(l_hash_str){
+        dap_chain_node_cli_set_reply_text(str_reply, "cond create succefully hash=%s\n", l_hash_str);
+        l_ret = 0;
+    }
+    else{
+        dap_chain_node_cli_set_reply_text(str_reply, "cond can't create\n");
+        l_ret = -1;
+    }
+
+    DAP_DELETE(l_hash_str);
+    return  l_ret;
+}
+
+/**
+ * @brief com_mempool_add_ca
+ * @details Place public CA into the mempool
+ * @param a_argc
+ * @param a_argv
+ * @param a_arg_func
+ * @param a_str_reply
+ * @return
+ */
+int com_mempool_add_ca( int a_argc,  char ** a_argv, void *a_arg_func, char ** a_str_reply)
+{
+    UNUSED(a_arg_func);
+    int arg_index = 1;
+
+    // Read params
+    const char * l_ca_name = NULL;
+    dap_chain_net_t * l_net = NULL;
+    dap_chain_t * l_chain = NULL;
+
+    dap_chain_node_cli_find_option_val(a_argv, arg_index, a_argc, "-ca_name", &l_ca_name);
+    dap_chain_node_cli_cmd_values_parse_net_chain(&arg_index,a_argc, a_argv, a_str_reply, &l_chain, &l_net);
+
+    // Check for network if was set or not
+    if ( l_net == NULL ){
+        dap_chain_node_cli_set_reply_text(a_str_reply,
+                "mempool_add_ca_public requires parameter '-net' to specify the chain network name");
+        return -1;
+    }
+
+    // Chech for chain if was set or not
+    if ( l_chain == NULL){
+       // If wasn't set - trying to auto detect
+        l_chain = dap_chain_net_get_chain_by_chain_type( l_net, DAP_CHAIN_DATUM_CA );
+        if (l_chain == NULL) { // If can't auto detect
+            dap_chain_node_cli_set_reply_text(a_str_reply,
+                    "No chains for CA datum in network \"%s\"", l_net->pub.name );
+            return -2;
+        }
+    }
+    // Check if '-name' wasn't specified
+    if (l_ca_name == NULL){
+        dap_chain_node_cli_set_reply_text(a_str_reply,
+                "mempool_add_ca_public requires parameter '-name' to specify the certificate name");
+        return -3;
+    }
+
+    // Find certificate with specified key
+    dap_cert_t * l_cert = dap_cert_find_by_name( l_ca_name );
+    if( l_cert == NULL ){
+        dap_chain_node_cli_set_reply_text(a_str_reply,
+                "Can't find \"%s\" certificate", l_ca_name );
+        return -4;
+    }
+    if( l_cert->enc_key == NULL ){
+        dap_chain_node_cli_set_reply_text(a_str_reply,
+                "Corrupted certificate \"%s\" without keys certificate", l_ca_name );
+        return -5;
+    }
+
+    if ( l_cert->enc_key->priv_key_data_size || l_cert->enc_key->priv_key_data){
+        dap_chain_node_cli_set_reply_text(a_str_reply,
+                "Certificate \"%s\" has private key data. Please export public only key certificate without private keys", l_ca_name );
+        return -6;
+    }
+
+    // Serialize certificate into memory
+    uint32_t l_cert_serialized_size = 0;
+    byte_t * l_cert_serialized = dap_cert_mem_save( l_cert, &l_cert_serialized_size );
+    if( l_cert_serialized == NULL){
+        dap_chain_node_cli_set_reply_text(a_str_reply,
+                "Can't serialize in memory certificate \"%s\"", l_ca_name );
+        return -7;
+    }
+    // Now all the chechs passed, forming datum for mempool
+    dap_chain_datum_t * l_datum = dap_chain_datum_create( DAP_CHAIN_DATUM_CA, l_cert_serialized , l_cert_serialized_size);
+    DAP_DELETE( l_cert_serialized);
+    if( l_datum == NULL){
+        dap_chain_node_cli_set_reply_text(a_str_reply,
+                "Can't produce datum from certificate \"%s\"", l_ca_name );
+        return -7;
+    }
+
+    // Finaly add datum to mempool
+    if ( dap_chain_mempool_datum_add ( l_datum,l_chain ) == 0 ){
+        return 0;
+    }else{
+        DAP_DELETE( l_datum );
+        return -8;
+    }
+}
+
+
+/**
+ * com_tx_create command
+ *
+ * Create transaction
+ */
+int com_tx_create(int argc, char ** argv, void *arg_func, char **str_reply)
+{
+    int arg_index = 1;
+//    int cmd_num = 1;
+//    const char *value_str = NULL;
+    const char *addr_base58_to = NULL;
+    const char *addr_base58_fee = NULL;
+    const char *str_tmp = NULL;
+    const char * l_from_wallet_name = NULL;
+    const char * l_token_ticker = NULL;
+    const char * l_net_name = NULL;
+    const char * l_chain_name = NULL;
+    const char * l_tx_num_str = NULL;
+    size_t l_tx_num = 0;
+
+    uint64_t value = 0;
+    uint64_t value_fee = 0;
+    dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-from_wallet", &l_from_wallet_name);
+    dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-to_addr", &addr_base58_to);
+    dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-token", &l_token_ticker);
+    dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-net", &l_net_name);
+    dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-chain", &l_chain_name);
+    dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-tx_num", &l_tx_num_str);
+
+    if(l_tx_num_str)
+        l_tx_num = strtoul(l_tx_num_str, NULL, 10);
+
+    if(dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-fee", &addr_base58_fee)) {
+        if(dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-value_fee", &str_tmp)) {
+            value_fee = strtoull(str_tmp, NULL, 10);
+        }
+    }
+    if(dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-value", &str_tmp)) {
+        value = strtoull(str_tmp, NULL, 10);
+    }
+    if(!l_from_wallet_name) {
+        dap_chain_node_cli_set_reply_text(str_reply, "tx_create requires parameter '-from_wallet'");
+        return -1;
+    }
+    if(!addr_base58_to) {
+        dap_chain_node_cli_set_reply_text(str_reply, "tx_create requires parameter '-to_addr'");
+        return -1;
+    }
+    if(!value) {
+        dap_chain_node_cli_set_reply_text(str_reply, "tx_create requires parameter '-value'");
+        return -1;
+    }
+    if(addr_base58_fee && !value_fee) {
+        dap_chain_node_cli_set_reply_text(str_reply,
+                "tx_create requires parameter '-value_fee' if '-fee' is specified");
+        return -1;
+    }
+
+    if(!l_net_name) {
+        dap_chain_node_cli_set_reply_text(str_reply, "tx_create requires parameter '-net'");
+        return -1;
+    }
+    dap_chain_net_t * l_net = dap_chain_net_by_name(l_net_name);
+    dap_ledger_t *l_ledger = l_net ? l_net->pub.ledger : NULL;
+    if((l_ledger = dap_chain_ledger_by_net_name(l_net_name)) == NULL) {
+        dap_chain_node_cli_set_reply_text(str_reply, "not found net by name '%s'", l_net_name);
+        return -1;
+    }
+
+    /*    if(!l_chain_name) {
+     dap_chain_node_cli_set_reply_text(str_reply, "tx_create requires parameter '-chain'");
+     return -1;
+     }*/
+    dap_chain_t * l_chain = dap_chain_net_get_chain_by_name(l_net, l_chain_name);
+    if(!l_chain) {
+        l_chain = dap_chain_net_get_chain_by_chain_type(l_net, CHAIN_TYPE_TX);
+    }
+    if(!l_chain) {
+        dap_chain_node_cli_set_reply_text(str_reply, "not found chain name '%s', try use parameter '-chain'",
+                l_chain_name);
+        return -1;
+    }
+
+    const char *c_wallets_path = dap_chain_wallet_get_path(g_config);
+    dap_chain_wallet_t * l_wallet = dap_chain_wallet_open(l_from_wallet_name, c_wallets_path);
+
+    if(!l_wallet) {
+        dap_chain_node_cli_set_reply_text(str_reply, "wallet %s does not exist", l_from_wallet_name);
+        return -1;
+    }
+    const dap_chain_addr_t *addr_from = (const dap_chain_addr_t *) dap_chain_wallet_get_addr(l_wallet, l_net->pub.id);
+    dap_chain_addr_t *addr_to = dap_chain_addr_from_str(addr_base58_to);
+    dap_chain_addr_t *addr_fee = dap_chain_addr_from_str(addr_base58_fee);
+
+    if(!addr_from) {
+        dap_chain_node_cli_set_reply_text(str_reply, "source address is invalid");
+        return -1;
+    }
+    if(!addr_to) {
+        dap_chain_node_cli_set_reply_text(str_reply, "destination address is invalid");
+        return -1;
+    }
+    if(addr_base58_fee && !addr_fee) {
+        dap_chain_node_cli_set_reply_text(str_reply, "fee address is invalid");
+        return -1;
+    }
+
+    dap_string_t *string_ret = dap_string_new(NULL);
+    //g_string_printf(string_ret, "from=%s\nto=%s\nval=%lld\nfee=%s\nval_fee=%lld\n\n",
+    //        addr_base58_from, addr_base58_to, value, addr_base58_fee, value_fee);
+
+    int res =
+            l_tx_num ?
+                       dap_chain_mempool_tx_create_massive(l_chain, dap_chain_wallet_get_key(l_wallet, 0), addr_from,
+                               addr_to, addr_fee,
+                               l_token_ticker, value, value_fee, l_tx_num)
+                               :
+                       dap_chain_mempool_tx_create(l_chain, dap_chain_wallet_get_key(l_wallet, 0), addr_from, addr_to,
+                               addr_fee,
+                               l_token_ticker, value, value_fee);
+    dap_string_append_printf(string_ret, "transfer=%s\n",
+            (res == 0) ? "Ok" : (res == -2) ? "False, not enough funds for transfer" : "False");
+
+    dap_chain_node_cli_set_reply_text(str_reply, string_ret->str);
+    dap_string_free(string_ret, false);
+
+    DAP_DELETE(addr_to);
+    DAP_DELETE(addr_fee);
+    dap_chain_wallet_close(l_wallet);
+    return res;
+}
+
+/**
+ * tx_verify command
+ *
+ * Verifing transaction
+ */
+int com_tx_verify(int argc, char ** argv, void *arg_func, char **str_reply)
+{
+    if(argc > 1) {
+        if(str_reply)
+            dap_chain_node_cli_set_reply_text(str_reply, "command \"%s\" not recognized", argv[1]);
+    }
+    if(str_reply)
+        dap_chain_node_cli_set_reply_text(str_reply, "command not defined, enter \"help <cmd name>\"");
+    return -1;
+}
+
+/**
+ * tx_history command
+ *
+ * Transaction history for an address
+ */
+int com_tx_history(int argc, char ** argv, void *arg_func, char **str_reply)
+{
+    int arg_index = 1;
+    const char *l_addr_base58 = NULL;
+    const char *l_wallet_name = NULL;
+    const char *l_net_str = NULL;
+    const char *l_chain_str = NULL;
+    const char *l_tx_hash_str = NULL;
+
+    dap_chain_t * l_chain = NULL;
+    dap_chain_net_t * l_net = NULL;
+
+    dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-addr", &l_addr_base58);
+    dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-w", &l_wallet_name);
+    dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-net", &l_net_str);
+    dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-chain", &l_chain_str);
+    dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-tx", &l_tx_hash_str);
+
+    if(!l_addr_base58 && !l_wallet_name && !l_tx_hash_str) {
+        dap_chain_node_cli_set_reply_text(str_reply, "tx_history requires parameter '-addr' or '-w' or '-tx'");
+        return -1;
+    }
+
+    // Select chain network
+    if(!l_net_str) {
+        dap_chain_node_cli_set_reply_text(str_reply, "tx_history requires parameter '-net'");
+        return -2;
+    } else {
+        if((l_net = dap_chain_net_by_name(l_net_str)) == NULL) { // Can't find such network
+            dap_chain_node_cli_set_reply_text(str_reply,
+                    "tx_history requires parameter '-net' to be valid chain network name");
+            return -3;
+        }
+    }
+    //Select chain emission
+    if(!l_chain_str) {
+        dap_chain_node_cli_set_reply_text(str_reply, "tx_history requires parameter '-chain'");
+        return -4;
+    } else {
+        if((l_chain = dap_chain_net_get_chain_by_name(l_net, l_chain_str)) == NULL) { // Can't find such chain
+            dap_chain_node_cli_set_reply_text(str_reply,
+                    "tx_history requires parameter '-chain' to be valid chain name in chain net %s",
+                    l_net_str);
+            return -5;
+        }
+    }
+    //char *l_group_mempool = dap_chain_net_get_gdb_group_mempool(l_chain);
+    //const char *l_chain_group = dap_chain_gdb_get_group(l_chain);
+
+    dap_chain_hash_fast_t l_tx_hash;
+    if(l_tx_hash_str) {
+        if(dap_chain_str_to_hash_fast(l_tx_hash_str, &l_tx_hash) < 0) {
+            l_tx_hash_str = NULL;
+            dap_chain_node_cli_set_reply_text(str_reply, "tx hash not recognized");
+            return -1;
+        }
+//        char hash_str[99];
+//        dap_chain_hash_fast_to_str(&l_tx_hash, hash_str,99);
+//        int gsdgsd=523;
+    }
+    dap_chain_addr_t *l_addr = NULL;
+    // if need addr
+    if(!l_tx_hash_str) {
+        if(l_wallet_name) {
+            const char *c_wallets_path = dap_chain_wallet_get_path(g_config);
+            dap_chain_wallet_t * l_wallet = dap_chain_wallet_open(l_wallet_name, c_wallets_path);
+            if(l_wallet) {
+                dap_chain_addr_t *l_addr_tmp = (dap_chain_addr_t *) dap_chain_wallet_get_addr(l_wallet, l_net->pub.id);
+                l_addr = DAP_NEW_SIZE(dap_chain_addr_t, sizeof(dap_chain_addr_t));
+                memcpy(l_addr, l_addr_tmp, sizeof(dap_chain_addr_t));
+                dap_chain_wallet_close(l_wallet);
+            }
+        }
+        if(!l_addr && l_addr_base58) {
+            l_addr = dap_chain_addr_from_str(l_addr_base58);
+        }
+        if(!l_addr && !l_tx_hash_str) {
+            dap_chain_node_cli_set_reply_text(str_reply, "wallet address not recognized");
+            return -1;
+        }
+    }
+
+    char *l_str_out = l_tx_hash_str ?
+                                      dap_db_history_tx(&l_tx_hash, l_chain) :
+                                      dap_db_history_addr(l_addr, l_chain);
+
+    char *l_str_ret = NULL;
+    if(l_tx_hash_str) {
+        l_str_ret = dap_strdup_printf("history for tx hash %s:\n%s", l_tx_hash_str,
+                l_str_out ? l_str_out : " empty");
+    }
+    else if(l_addr) {
+        char *l_addr_str = dap_chain_addr_to_str(l_addr);
+        l_str_ret = dap_strdup_printf("history for addr %s:\n%s", l_addr_str,
+                l_str_out ? l_str_out : " empty");
+        DAP_DELETE(l_addr_str);
+    }
+    dap_chain_node_cli_set_reply_text(str_reply, l_str_ret);
+    DAP_DELETE(l_str_out);
+    DAP_DELETE(l_str_ret);
+    return 0;
+}
+
+/**
+ * stats command
+ */
+int com_stats(int argc, char ** argv, void *arg_func, char **str_reply)
+{
+    enum {
+        CMD_NONE, CMD_STATS_CPU
+    };
+    int arg_index = 1;
+    int cmd_num = CMD_NONE;
+    // find  add parameter ('cpu')
+    if (dap_chain_node_cli_find_option_val(argv, arg_index, min(argc, arg_index + 1), "cpu", NULL)) {
+        cmd_num = CMD_STATS_CPU;
+    }
+    switch (cmd_num) {
+    case CMD_NONE:
+    default:
+        dap_chain_node_cli_set_reply_text(str_reply, "format of command: stats cpu");
+        return -1;
+    case CMD_STATS_CPU:
+#if (defined DAP_OS_UNIX) || (defined __WIN32)
+    {
+        dap_cpu_monitor_init();
+        usleep(500000);
+        char *str_reply_prev = dap_strdup_printf("");
+        char *str_delimiter;
+        dap_cpu_stats_t s_cpu_stats = dap_cpu_get_stats();
+        for (int n_cpu_num = 0; n_cpu_num < s_cpu_stats.cpu_cores_count; n_cpu_num++) {
+            if ((n_cpu_num % 4 == 0) && (n_cpu_num != 0)) {
+                str_delimiter = dap_strdup_printf("\n");
+            } else if (n_cpu_num == s_cpu_stats.cpu_cores_count - 1) {
+                str_delimiter = dap_strdup_printf("");
+            } else {
+                str_delimiter = dap_strdup_printf(" ");
+            }
+            *str_reply = dap_strdup_printf("%sCPU-%d: %f%%%s", str_reply_prev, n_cpu_num, s_cpu_stats.cpus[n_cpu_num].load, str_delimiter);
+            DAP_DELETE(str_reply_prev);
+            DAP_DELETE(str_delimiter);
+            str_reply_prev = *str_reply;
+        }
+        *str_reply = dap_strdup_printf("%s\nTotal: %f%%", str_reply_prev, s_cpu_stats.cpu_summary.load);
+        DAP_DELETE(str_reply_prev);
+        break;
+    }
+#else
+        dap_chain_node_cli_set_reply_text(str_reply, "only Linux or Windows environment supported");
+        return -1;
+#endif // DAP_OS_UNIX
+    }
+    return 0;
+}
+
+int com_exit(int argc, char ** argv, void *arg_func, char **str_reply)
+{
+    exit(0);
+}
+
+
+/**
+ * print_log command
+ *
+ * Print log info
+ * print_log [ts_after <timestamp >] [limit <line numbers>]
+ */
+int com_print_log(int argc, char ** argv, void *arg_func, char **str_reply)
+{
+    int arg_index = 1;
+    const char * l_str_ts_after = NULL;
+    const char * l_str_limit = NULL;
+    int64_t l_ts_after = 0;
+    long l_limit = 0;
+    dap_chain_node_cli_find_option_val(argv, arg_index, argc, "ts_after", &l_str_ts_after);
+    dap_chain_node_cli_find_option_val(argv, arg_index, argc, "limit", &l_str_limit);
+
+    l_ts_after = (l_str_ts_after) ? strtoll(l_str_ts_after, 0, 10) : -1;
+    l_limit = (l_str_limit) ? strtol(l_str_limit, 0, 10) : -1;
+
+    if(l_ts_after < 0 || !l_str_ts_after) {
+        dap_chain_node_cli_set_reply_text(str_reply, "requires valid parameter 'l_ts_after'");
+        return -1;
+    }
+    if(!l_limit) {
+        dap_chain_node_cli_set_reply_text(str_reply, "requires valid parameter 'limit'");
+        return -1;
+    }
+
+    // get logs from list
+    char *l_str_ret = dap_log_get_item(l_ts_after, (int) l_limit);
+    if(!l_str_ret) {
+        dap_chain_node_cli_set_reply_text(str_reply, "no logs");
+        return -1;
+    }
+    dap_chain_node_cli_set_reply_text(str_reply, l_str_ret);
+    DAP_DELETE(l_str_ret);
+    return 0;
+}
+
+/**
+ * vpn_client command
+ *
+ * VPN client control
+ */
+int com_vpn_client(int a_argc, char ** a_argv, void *arg_func, char **a_str_reply)
+{
+#ifndef _WIN32
+    enum {
+        CMD_NONE, CMD_START, CMD_STOP, CMD_STATUS
+    };
+    int l_arg_index = 1;
+    // find net
+    dap_chain_net_t *l_net = NULL;
+    if(dap_chain_node_cli_cmd_values_parse_net_chain(&l_arg_index, a_argc, a_argv, a_str_reply, NULL, &l_net) < 0)
+        return -2;
+
+    int cmd_num = CMD_NONE;
+    if(dap_chain_node_cli_find_option_val(a_argv, l_arg_index, min(a_argc, l_arg_index + 1), "start", NULL)) {
+        cmd_num = CMD_START;
+    }
+    else if(dap_chain_node_cli_find_option_val(a_argv, l_arg_index, min(a_argc, l_arg_index + 1), "stop", NULL)) {
+        cmd_num = CMD_STOP;
+    }
+    else if(dap_chain_node_cli_find_option_val(a_argv, l_arg_index, min(a_argc, l_arg_index + 1), "status", NULL)) {
+        cmd_num = CMD_STATUS;
+    }
+    if(cmd_num == CMD_NONE) {
+        if(!a_argv[1])
+            dap_chain_node_cli_set_reply_text(a_str_reply, "invalid parameters");
+        else
+            dap_chain_node_cli_set_reply_text(a_str_reply, "parameter %s not recognized", a_argv[1]);
+        return -1;
+    }
+
+    switch (cmd_num)
+    {
+    case CMD_START: {
+        const char * l_str_addr = NULL; // for example, "192.168.100.93"
+        const char * l_str_port = NULL; // for example, "8079"
+        dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-addr", &l_str_addr);
+        if(!l_str_addr) {
+            dap_chain_node_cli_set_reply_text(a_str_reply,
+                    "VPN server address not defined, use -addr <vpn server ipv4 address> parameter");
+            break;
+        }
+        dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-port", &l_str_port);
+        int l_srv_port = (l_str_port) ? (int) strtoll(l_str_port, 0, 10) : 0;
+        if(!l_srv_port) {
+            dap_chain_node_cli_set_reply_text(a_str_reply,
+                    "VPN server port not defined, use -port <vpn server port>  parameter");
+            break;
+        }
+        int l_res = dap_chain_net_vpn_client_start(l_net, l_str_addr, NULL, l_srv_port);
+        switch (l_res) {
+        case 0:
+            dap_chain_node_cli_set_reply_text(a_str_reply, "VPN client started successfully");
+            break;
+        case 1:
+            dap_chain_node_cli_set_reply_text(a_str_reply, "VPN client already started");
+            break;
+        case -2:
+        case -3:
+            dap_chain_node_cli_set_reply_text(a_str_reply, "Can't connect to VPN server");
+            break;
+        default:
+            dap_chain_node_cli_set_reply_text(a_str_reply, "Can't start VPN client");
+            break;
+        }
+        return l_res;
+    }
+        break;
+    case CMD_STOP: {
+        int res = dap_chain_net_vpn_client_stop();
+        if(!res)
+            dap_chain_node_cli_set_reply_text(a_str_reply, "VPN client stopped successfully");
+        else
+            dap_chain_node_cli_set_reply_text(a_str_reply, "VPN client not stopped");
+        return res;
+    }
+        //break;
+    case CMD_STATUS:
+        switch (dap_chain_net_vpn_client_status()) {
+//        switch (0){
+        case 0:
+            dap_chain_node_cli_set_reply_text(a_str_reply, "VPN client stopped");
+            return 0;
+        case 1:
+            dap_chain_node_cli_set_reply_text(a_str_reply, "VPN client started");
+            return 0;
+        case -1:
+            dap_chain_node_cli_set_reply_text(a_str_reply, "Can't get VPN state");
+            return -1;
+        }
+        break;
+    }
+#endif
+    return 0;
+}
diff --git a/libdap-chain-net/dap_chain_node_cli_cmd.h b/libdap-chain-net/dap_chain_node_cli_cmd.h
new file mode 100644
index 0000000000000000000000000000000000000000..b03b3b1342cf495513a07d4d3f6092869327a794
--- /dev/null
+++ b/libdap-chain-net/dap_chain_node_cli_cmd.h
@@ -0,0 +1,146 @@
+/*
+ * Authors:
+ * Dmitriy A. Gerasimov <gerasimov.dmitriy@demlabs.net>
+ * Alexander Lysikov <alexander.lysikov@demlabs.net>
+ * DeM Labs Inc.   https://demlabs.net
+ * Kelvin Project https://github.com/kelvinblockchain
+ * Copyright  (c) 2019
+ * All rights reserved.
+
+ This file is part of DAP (Deus Applications Prototypes) the open source project
+
+ DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ DAP is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with any DAP based project.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "dap_chain.h"
+#include "dap_chain_net.h"
+#include "dap_chain_node.h"
+#include "dap_chain_node_cli.h"
+
+/**
+ * Find in base addr by alias
+ *
+ * return addr, NULL if not found
+ */
+dap_chain_node_addr_t* dap_chain_node_addr_get_by_alias(dap_chain_net_t * a_net, const char *alias);
+
+
+int dap_chain_node_cli_cmd_values_parse_net_chain(int *a_arg_index,int argc, char ** argv, char ** a_str_reply,
+                             dap_chain_t ** a_chain, dap_chain_net_t ** a_net);
+
+/**
+ * global_db command
+ */
+int com_global_db(int argc,  char ** argv, void *arg_func, char **str_reply);
+
+/**
+ * Node command
+ */
+int com_node(int argc,  char ** argv, void *arg_func, char **str_reply);
+
+/**
+ * Traceroute command
+ *
+ * return 0 OK, -1 Err
+ */
+int com_traceroute(int argc,  char** argv, void *arg_func, char **str_reply);
+
+/**
+ * Tracepath command
+ *
+ * return 0 OK, -1 Err
+ */
+int com_tracepath(int argc,  char** argv, void *arg_func, char **str_reply);
+
+/**
+ * Ping command
+ *
+ * return 0 OK, -1 Err
+ */
+int com_ping(int argc,  char** argv, void *arg_func, char **str_reply);
+
+/**
+ * Help command
+ */
+int com_help(int argc,  char ** argv, void *arg_func, char **str_reply);
+
+
+/**
+ * Token declaration
+ */
+int com_token_decl ( int argc,  char ** argv, void *arg_func, char ** str_reply);
+
+int com_token_decl_update(int argc, char ** argv, void *arg_func, char ** a_str_reply);
+
+/**
+ * Token declaration add sign
+ */
+int com_token_decl_sign ( int argc,  char ** argv, void *arg_func, char ** str_reply);
+
+/**
+ * Token emission
+ */
+int com_token_emit (int argc,  char ** argv, void *arg_func, char ** str_reply);
+
+
+/**
+ * com_tx_create command
+ *
+ * Wallet info
+ */
+int com_tx_wallet(int argc, char ** argv, void *arg_func, char **str_reply);
+
+/**
+ * com_tx_create command
+ *
+ * Create transaction
+ */
+int com_tx_create(int argc, char ** argv, void *arg_func, char **str_reply);
+int com_tx_cond_create(int argc, char ** argv, void *arg_func, char **str_reply);
+
+/**
+ * tx_verify command
+ *
+ * Verifing transaction
+ */
+int com_tx_verify(int argc, char ** argv, void *arg_func, char **str_reply);
+
+/**
+ * tx_history command
+ *
+ * Transaction history for an address
+ */
+int com_tx_history(int argc, char ** argv, void *arg_func, char **str_reply);
+
+// Print log info
+int com_print_log(int argc, char ** argv, void *arg_func, char **str_reply);
+
+// Print statistics
+int com_stats(int argc, char ** argv, void *arg_func, char **str_reply);
+
+int com_exit(int argc, char ** argv, void *arg_func, char **str_reply);
+
+// vpn_client command
+int com_vpn_client(int a_argc, char ** a_argv, void *arg_func, char **a_str_reply);
+
+int com_mempool_delete(int argc, char ** argv, void *arg_func, char ** a_str_reply);
+int com_mempool_list(int argc, char ** argv, void *arg_func, char ** a_str_reply);
+int com_mempool_proc(int argc, char ** argv, void *arg_func, char ** a_str_reply);
+
+/**
+ * Place public CA into the mempool
+ */
+int com_mempool_add_ca( int a_argc,  char ** a_argv, void *a_arg_func, char ** a_str_reply);
diff --git a/libdap-chain-net/dap_chain_node_cli_cmd_tx.c b/libdap-chain-net/dap_chain_node_cli_cmd_tx.c
new file mode 100644
index 0000000000000000000000000000000000000000..32ea1ae2e53b6934179c906c5133317717deb78f
--- /dev/null
+++ b/libdap-chain-net/dap_chain_node_cli_cmd_tx.c
@@ -0,0 +1,643 @@
+/*
+ * Authors:
+ * Alexander Lysikov <alexander.lysikov@demlabs.net>
+ * DeM Labs Inc.   https://demlabs.net
+ * Kelvin Project https://github.com/kelvinblockchain
+ * Copyright  (c) 2019
+ * All rights reserved.
+
+ This file is part of DAP (Deus Applications Prototypes) the open source project
+
+ DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ DAP is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with any DAP based project.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <pthread.h>
+
+#include <dap_common.h>
+#include <dap_strfuncs.h>
+#include <dap_string.h>
+#include <dap_list.h>
+#include <dap_hash.h>
+
+#include "dap_chain_datum_tx_items.h"
+
+#include "dap_chain_node_cli_cmd_tx.h"
+
+#define LOG_TAG "chain_node_cli_cmd_tx"
+
+#include "uthash.h"
+// for dap_db_history_filter()
+typedef struct dap_tx_data {
+    dap_chain_hash_fast_t tx_hash;
+    char tx_hash_str[70];
+    char token_ticker[DAP_CHAIN_TICKER_SIZE_MAX];
+    //size_t obj_num;
+    size_t pos_num;
+    dap_chain_datum_t *datum;
+    dap_chain_addr_t addr;
+    bool is_use_all_cur_out;// find cur addr in prev OUT items
+    UT_hash_handle hh;
+} dap_tx_data_t;
+
+/*static char* dap_db_new_history_timestamp()
+{
+    static pthread_mutex_t s_mutex = PTHREAD_MUTEX_INITIALIZER;
+    // get unique key
+    pthread_mutex_lock(&s_mutex);
+    static time_t s_last_time = 0;
+    static uint64_t s_suffix = 0;
+    time_t l_cur_time = time(NULL);
+    if(s_last_time == l_cur_time)
+        s_suffix++;
+    else {
+        s_suffix = 0;
+        s_last_time = l_cur_time;
+    }
+    char *l_str = dap_strdup_printf("%lld_%lld", (uint64_t) l_cur_time, s_suffix);
+    pthread_mutex_unlock(&s_mutex);
+    return l_str;
+}*/
+
+// for dap_db_history_tx & dap_db_history_addr()
+static dap_chain_datum_t* get_prev_tx(dap_tx_data_t *a_tx_data)
+{
+    if(!a_tx_data)
+        return NULL;
+    dap_chain_datum_t *l_datum = a_tx_data->datum;
+    return l_datum;
+}
+
+/**
+ * Get data according the history log
+ *
+ * return history string
+ */
+char* dap_db_history_tx(dap_chain_hash_fast_t* a_tx_hash, dap_chain_t * a_chain)
+{
+    dap_string_t *l_str_out = dap_string_new(NULL);
+
+    bool l_tx_hash_found = false;
+    dap_tx_data_t *l_tx_data_hash = NULL;
+    // load transactions
+    dap_chain_atom_iter_t *l_atom_iter = a_chain->callback_atom_iter_create(a_chain);
+    dap_chain_atom_ptr_t *l_atom = a_chain->callback_atom_iter_get_first(l_atom_iter);
+    size_t l_atom_size = a_chain->callback_atom_get_size(l_atom);
+
+    while(l_atom && l_atom_size) {
+        dap_chain_datum_t *l_datum = (dap_chain_datum_t*) l_atom;
+        if(!l_datum && l_datum->header.type_id != DAP_CHAIN_DATUM_TX) {
+            // go to next transaction
+            l_atom = a_chain->callback_atom_iter_get_next(l_atom_iter);
+            l_atom_size = a_chain->callback_atom_get_size(l_atom);
+            continue;
+        }
+        dap_tx_data_t *l_tx_data = NULL;
+
+        // transaction
+        dap_chain_datum_tx_t *l_tx = (dap_chain_datum_tx_t*) l_datum->data;
+
+        // find Token items - present in emit transaction
+        dap_list_t *l_list_tx_token = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_TOKEN, NULL);
+
+        // find OUT items
+        dap_list_t *l_list_out_items = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_OUT, NULL);
+        dap_list_t *l_list_tmp = l_list_out_items;
+        while(l_list_tmp) {
+            const dap_chain_tx_out_t *l_tx_out = (const dap_chain_tx_out_t*) l_list_tmp->data;
+            // save OUT item l_tx_out - only for first OUT item
+            if(!l_tx_data)
+            {
+                // save tx hash
+                l_tx_data = DAP_NEW_Z(dap_tx_data_t);
+                dap_chain_hash_fast_t l_tx_hash;
+                dap_hash_fast(l_tx, dap_chain_datum_tx_get_size(l_tx), &l_tx_hash);
+                memcpy(&l_tx_data->tx_hash, &l_tx_hash, sizeof(dap_chain_hash_fast_t));
+                memcpy(&l_tx_data->addr, &l_tx_out->addr, sizeof(dap_chain_addr_t));
+                dap_chain_hash_fast_to_str(&l_tx_data->tx_hash, l_tx_data->tx_hash_str,
+                        sizeof(l_tx_data->tx_hash_str));
+                //l_tx_data->pos_num = l_count;
+                //l_tx_data->datum = l_datum;
+                l_tx_data->datum = DAP_NEW_SIZE(dap_chain_datum_t, l_atom_size);
+                memcpy(l_tx_data->datum, l_datum, l_atom_size);
+                // save token name
+                if(l_list_tx_token) {
+                    dap_chain_tx_token_t *tk = l_list_tx_token->data;
+                    memcpy(l_tx_data->token_ticker, tk->header.ticker, sizeof(l_tx_data->token_ticker));
+                }
+                // take token from prev out item
+                else {
+
+                    // find IN items
+                    dap_list_t *l_list_in_items = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_IN, NULL);
+                    dap_list_t *l_list_tmp_in = l_list_in_items;
+                    // find token_ticker in prev OUT items
+                    while(l_list_tmp_in) {
+                        const dap_chain_tx_in_t *l_tx_in =
+                                (const dap_chain_tx_in_t*) l_list_tmp_in->data;
+                        dap_chain_hash_fast_t tx_prev_hash = l_tx_in->header.tx_prev_hash;
+
+                        //find prev OUT item
+                        dap_tx_data_t *l_tx_data_prev = NULL;
+                        HASH_FIND(hh, l_tx_data_hash, &tx_prev_hash, sizeof(dap_chain_hash_fast_t),
+                                l_tx_data_prev);
+                        if(l_tx_data_prev != NULL) {
+                            // fill token in l_tx_data from prev transaction
+                            if(l_tx_data) {
+                                // get token from prev tx
+                                memcpy(l_tx_data->token_ticker, l_tx_data_prev->token_ticker,
+                                        sizeof(l_tx_data->token_ticker));
+                                break;
+                            }
+                            l_list_tmp_in = dap_list_next(l_list_tmp_in);
+                        }
+                    }
+                    if(l_list_in_items)
+                        dap_list_free(l_list_in_items);
+                }
+                HASH_ADD(hh, l_tx_data_hash, tx_hash, sizeof(dap_chain_hash_fast_t), l_tx_data);
+            }
+            l_list_tmp = dap_list_next(l_list_tmp);
+        }
+        if(l_list_out_items)
+            dap_list_free(l_list_out_items);
+
+        // calc hash
+        dap_chain_hash_fast_t l_tx_hash;
+        dap_hash_fast(l_tx, dap_chain_datum_tx_get_size(l_tx), &l_tx_hash);
+        // search tx with a_tx_hash
+        if(!dap_hash_fast_compare(a_tx_hash, &l_tx_hash)) {
+            // go to next transaction
+            l_atom = a_chain->callback_atom_iter_get_next(l_atom_iter);
+            l_atom_size = a_chain->callback_atom_get_size(l_atom);
+            continue;
+        }
+        // found a_tx_hash now
+
+        // transaction time
+        if(l_tx->header.ts_created > 0) {
+            time_t rawtime = (time_t) l_tx->header.ts_created;
+            struct tm l_timeinfo = {0};
+            localtime_r(&rawtime, &l_timeinfo);
+            dap_string_append_printf(l_str_out, " %s", asctime(&l_timeinfo));
+        }
+
+        // find all OUT items in transaction
+        l_list_out_items = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_OUT, NULL);
+        l_list_tmp = l_list_out_items;
+        while(l_list_tmp) {
+            const dap_chain_tx_out_t *l_tx_out = (const dap_chain_tx_out_t*) l_list_tmp->data;
+            dap_tx_data_t *l_tx_data_prev = NULL;
+
+            const char *l_token_str = NULL;
+            if(l_tx_data)
+                l_token_str = l_tx_data->token_ticker;
+            char *l_dst_to_str =
+                    (l_tx_out) ? dap_chain_addr_to_str(&l_tx_out->addr) :
+                    NULL;
+            dap_string_append_printf(l_str_out, " OUT item %lld %s to %s\n",
+                    l_tx_out->header.value,
+                    dap_strlen(l_token_str) > 0 ? l_token_str : "?",
+                    l_dst_to_str ? l_dst_to_str : "?"
+                                   );
+            DAP_DELETE(l_dst_to_str);
+            l_list_tmp = dap_list_next(l_list_tmp);
+        }
+        // find all IN items in transaction
+        dap_list_t *l_list_in_items = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_IN, NULL);
+        l_list_tmp = l_list_in_items;
+        // find cur addr in prev OUT items
+        while(l_list_tmp) {
+            const dap_chain_tx_in_t *l_tx_in = (const dap_chain_tx_in_t*) l_list_tmp->data;
+            dap_chain_hash_fast_t tx_prev_hash = l_tx_in->header.tx_prev_hash;
+            char l_tx_hash_str[70];
+            if(!dap_hash_fast_is_blank(&tx_prev_hash))
+                dap_chain_hash_fast_to_str(&tx_prev_hash, l_tx_hash_str, sizeof(l_tx_hash_str));
+            else
+                strcpy(l_tx_hash_str, "Null");
+            dap_string_append_printf(l_str_out, " IN item \n  prev tx_hash %s\n", l_tx_hash_str);
+
+            //find prev OUT item
+            dap_tx_data_t *l_tx_data_prev = NULL;
+            HASH_FIND(hh, l_tx_data_hash, &tx_prev_hash, sizeof(dap_chain_hash_fast_t), l_tx_data_prev);
+            if(l_tx_data_prev != NULL) {
+
+                dap_chain_datum_t *l_datum_prev = get_prev_tx(l_tx_data_prev);
+                dap_chain_datum_tx_t *l_tx_prev =
+                        l_datum_prev ? (dap_chain_datum_tx_t*) l_datum_prev->data : NULL;
+
+                // find OUT items in prev datum
+                dap_list_t *l_list_out_prev_items = dap_chain_datum_tx_items_get(l_tx_prev,
+                        TX_ITEM_TYPE_OUT, NULL);
+                // find OUT item for IN item;
+                dap_list_t *l_list_out_prev_item = dap_list_nth(l_list_out_prev_items,
+                        l_tx_in->header.tx_out_prev_idx);
+                dap_chain_tx_out_t *l_tx_prev_out =
+                        l_list_out_prev_item ?
+                                               (dap_chain_tx_out_t*) l_list_out_prev_item->data :
+                                               NULL;
+                // print value from prev out item
+                dap_string_append_printf(l_str_out, "  prev OUT item value=%lld",
+                        l_tx_prev_out ? l_tx_prev_out->header.value : 0);
+            }
+            dap_string_append_printf(l_str_out, "\n");
+            l_list_tmp = dap_list_next(l_list_tmp);
+        }
+
+        if(l_list_tx_token)
+            dap_list_free(l_list_tx_token);
+        if(l_list_out_items)
+            dap_list_free(l_list_out_items);
+        if(l_list_in_items)
+            dap_list_free(l_list_in_items);
+        l_tx_hash_found = true;
+        break;
+
+        // go to next transaction
+        //l_atom = a_chain->callback_atom_iter_get_next(l_atom_iter);
+        //l_atom_size = a_chain->callback_atom_get_size(l_atom);
+    }
+    a_chain->callback_atom_iter_delete(l_atom_iter);
+
+    // delete hashes
+    dap_tx_data_t *l_iter_current, *l_item_tmp;
+    HASH_ITER(hh, l_tx_data_hash , l_iter_current, l_item_tmp)
+    {
+        // delete datum
+        DAP_DELETE(l_iter_current->datum);
+        // delete struct
+        DAP_DELETE(l_iter_current);
+        HASH_DEL(l_tx_data_hash, l_iter_current);
+    }
+
+    // if no history
+    if(!l_str_out->len)
+        dap_string_append(l_str_out, "empty");
+    char *l_ret_str = l_str_out ? dap_string_free(l_str_out, false) : NULL;
+    return l_ret_str;
+}
+
+/**
+ * Get data according the history log
+ *
+ * return history string
+ */
+char* dap_db_history_addr(dap_chain_addr_t * a_addr, dap_chain_t * a_chain)
+{
+    dap_string_t *l_str_out = dap_string_new(NULL);
+
+    dap_tx_data_t *l_tx_data_hash = NULL;
+    // load transactions
+    dap_chain_atom_iter_t *l_atom_iter = a_chain->callback_atom_iter_create(a_chain);
+    dap_chain_atom_ptr_t *l_atom = a_chain->callback_atom_iter_get_first(l_atom_iter);
+    if (!l_atom) {
+        return NULL;
+    }
+    size_t l_atom_size = a_chain->callback_atom_get_size(l_atom);
+
+    while(l_atom && l_atom_size) {
+        dap_chain_datum_t *l_datum = a_chain->callback_atom_get_datum ? a_chain->callback_atom_get_datum(l_atom) : (dap_chain_datum_t*)l_atom;
+        if(!l_datum || l_datum->header.type_id != DAP_CHAIN_DATUM_TX) {
+            // go to next transaction
+            l_atom = a_chain->callback_atom_iter_get_next(l_atom_iter);
+            l_atom_size = a_chain->callback_atom_get_size(l_atom);
+            continue;
+        }
+        // transaction
+        dap_chain_datum_tx_t *l_tx = (dap_chain_datum_tx_t*) l_datum->data;
+        dap_list_t *l_records_out = NULL;
+        // transaction time
+        char *l_time_str = NULL;
+        {
+            if(l_tx->header.ts_created > 0) {
+                time_t rawtime = (time_t) l_tx->header.ts_created;
+                struct tm * timeinfo;
+                timeinfo = localtime(&rawtime);
+                if(timeinfo)
+                    l_time_str = dap_strdup(asctime(timeinfo));
+            }
+            else
+                l_time_str = dap_strdup(" ");
+        }
+
+        // find Token items - present in emit transaction
+        dap_list_t *l_list_tx_token = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_TOKEN, NULL);
+
+        // list of dap_tx_data_t*; info about OUT item in current transaction
+        dap_list_t *l_list_out_info = NULL;
+
+        // find OUT items
+        dap_list_t *l_list_out_items = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_OUT, NULL);
+        dap_list_t *l_list_out_items_tmp = l_list_out_items;
+        while(l_list_out_items_tmp) {
+            const dap_chain_tx_out_t *l_tx_out = (const dap_chain_tx_out_t*) l_list_out_items_tmp->data;
+            // save OUT item l_tx_out
+            {
+                // save tx hash
+                // info about OUT item in current transaction
+                dap_tx_data_t *l_tx_data = DAP_NEW_Z(dap_tx_data_t);
+                dap_chain_hash_fast_t l_tx_hash;
+                dap_hash_fast(l_tx, dap_chain_datum_tx_get_size(l_tx), &l_tx_hash);
+                memcpy(&l_tx_data->tx_hash, &l_tx_hash, sizeof(dap_chain_hash_fast_t));
+                memcpy(&l_tx_data->addr, &l_tx_out->addr, sizeof(dap_chain_addr_t));
+                dap_chain_hash_fast_to_str(&l_tx_data->tx_hash, l_tx_data->tx_hash_str, sizeof(l_tx_data->tx_hash_str));
+                l_tx_data->datum = DAP_NEW_SIZE(dap_chain_datum_t, l_atom_size);
+                memcpy(l_tx_data->datum, l_datum, l_atom_size);
+                // save token name
+                if(l_tx_data && l_list_tx_token) {
+                    dap_chain_tx_token_t *tk = l_list_tx_token->data;
+                    memcpy(l_tx_data->token_ticker, tk->header.ticker, sizeof(l_tx_data->token_ticker));
+                }
+                HASH_ADD(hh, l_tx_data_hash, tx_hash, sizeof(dap_chain_hash_fast_t), l_tx_data);
+
+                // save OUT items to list
+                l_records_out = dap_list_append(l_records_out, (void*) l_tx_out);
+                // save info about OUT items to list
+                l_list_out_info = dap_list_append(l_list_out_info, (void*) l_tx_data);
+            }
+            l_list_out_items_tmp = dap_list_next(l_list_out_items_tmp);
+        }
+
+        // find IN items
+        dap_list_t *l_list_in_items = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_IN, NULL);
+        dap_list_t *l_list_in_items_tmp = l_list_in_items;
+        // find cur addr in prev OUT items
+        //bool l_is_use_all_cur_out = false;
+        {
+            while(l_list_in_items_tmp) {
+                const dap_chain_tx_in_t *l_tx_in = (const dap_chain_tx_in_t*) l_list_in_items_tmp->data;
+                dap_chain_hash_fast_t tx_prev_hash = l_tx_in->header.tx_prev_hash;
+
+                //find prev OUT item
+                dap_tx_data_t *l_tx_data_prev = NULL;
+                HASH_FIND(hh, l_tx_data_hash, &tx_prev_hash, sizeof(dap_chain_hash_fast_t), l_tx_data_prev);
+                if(l_tx_data_prev != NULL) {
+                    // fill token in all l_tx_data from prev transaction
+
+                    dap_list_t *l_list_out_info_tmp = l_list_out_info;
+                    while(l_list_out_info_tmp) {
+                        dap_tx_data_t *l_tx_data = (dap_tx_data_t*) l_list_out_info_tmp->data;
+                        if(l_tx_data) {
+                            // get token from prev tx
+                            memcpy(l_tx_data->token_ticker, l_tx_data_prev->token_ticker,
+                                    sizeof(l_tx_data->token_ticker));
+                            dap_chain_datum_t *l_datum_prev = get_prev_tx(l_tx_data_prev);
+                            dap_chain_datum_tx_t *l_tx_prev =
+                                    l_datum_prev ? (dap_chain_datum_tx_t*) l_datum_prev->data : NULL;
+
+                            // find OUT items in prev datum
+                            dap_list_t *l_list_out_prev_items = dap_chain_datum_tx_items_get(l_tx_prev,
+                                    TX_ITEM_TYPE_OUT, NULL);
+                            // find OUT item for IN item;
+                            dap_list_t *l_list_out_prev_item = dap_list_nth(l_list_out_prev_items,
+                                    l_tx_in->header.tx_out_prev_idx);
+                            dap_chain_tx_out_t *l_tx_prev_out =
+                                    l_list_out_prev_item ?
+                                                           (dap_chain_tx_out_t*) l_list_out_prev_item->data :
+                                                           NULL;
+                            if(l_tx_prev_out && !memcmp(&l_tx_prev_out->addr, a_addr, sizeof(dap_chain_addr_t)))
+                                l_tx_data->is_use_all_cur_out = true;
+
+                        }
+                        l_list_out_info_tmp = dap_list_next(l_list_out_info_tmp);
+                    }
+                }
+                l_list_in_items_tmp = dap_list_next(l_list_in_items_tmp);
+            }
+            // find prev OUT items for IN items
+            dap_list_t *l_list_in_items2_tmp = l_list_in_items; // go to begin of list
+            while(l_list_in_items2_tmp) {
+                const dap_chain_tx_in_t *l_tx_in = (const dap_chain_tx_in_t*) l_list_in_items2_tmp->data;
+                dap_chain_hash_fast_t tx_prev_hash = l_tx_in->header.tx_prev_hash;
+                // if first transaction - empty prev OUT item
+                if(dap_hash_fast_is_blank(&tx_prev_hash)) {
+
+                    dap_tx_data_t *l_tx_data = NULL;
+                    dap_list_t *l_list_out_info_tmp = l_list_out_info;
+                    while(l_list_out_info_tmp) {
+                        l_tx_data = (dap_tx_data_t*) l_list_out_info_tmp->data;
+                        if(l_tx_data->token_ticker[0])
+                            break;
+                        l_list_out_info_tmp = dap_list_next(l_list_out_info_tmp);
+                    }
+
+                    // add emit info to ret string
+                    if(l_tx_data && !memcmp(&l_tx_data->addr, a_addr, sizeof(dap_chain_addr_t))) {
+                        dap_list_t *l_records_tmp = l_records_out;
+                        while(l_records_tmp) {
+
+                            const dap_chain_tx_out_t *l_tx_out = (const dap_chain_tx_out_t*) l_records_tmp->data;
+                            dap_string_append_printf(l_str_out, "tx hash %s \n emit %lu %s\n",
+                                    l_tx_data->tx_hash_str,
+                                    l_tx_out->header.value,
+                                    l_tx_data->token_ticker);
+                            l_records_tmp = dap_list_next(l_records_tmp);
+                        }
+                    }
+                    //dap_list_free(l_records_out);
+                }
+                // in other transactions except first one
+                else {
+                    //find prev OUT item
+                    dap_tx_data_t *l_tx_data_prev = NULL;
+                    HASH_FIND(hh, l_tx_data_hash, &tx_prev_hash, sizeof(dap_chain_hash_fast_t), l_tx_data_prev);
+                    if(l_tx_data_prev != NULL) {
+                        char *l_src_str = NULL;
+                        bool l_src_str_is_cur = false;
+
+                        dap_tx_data_t *l_tx_data = NULL;
+                        dap_list_t *l_list_out_info_tmp = l_list_out_info;
+                        while(l_list_out_info_tmp) {
+                            l_tx_data = (dap_tx_data_t*) l_list_out_info_tmp->data;
+                            if(l_tx_data->token_ticker[0])
+                                break;
+                            l_list_out_info_tmp = dap_list_next(l_list_out_info_tmp);
+                        }
+                        if(l_tx_data) {
+                            // get token from prev tx
+                            memcpy(l_tx_data->token_ticker, l_tx_data_prev->token_ticker,
+                                    sizeof(l_tx_data->token_ticker));
+
+                            dap_chain_datum_t *l_datum_prev = get_prev_tx(l_tx_data_prev);
+                            dap_chain_datum_tx_t *l_tx_prev =
+                                    l_datum_prev ? (dap_chain_datum_tx_t*) l_datum_prev->data : NULL;
+
+                            // find OUT items in prev datum
+                            dap_list_t *l_list_out_prev_items = dap_chain_datum_tx_items_get(l_tx_prev,
+                                    TX_ITEM_TYPE_OUT, NULL);
+                            // find OUT item for IN item;
+                            dap_list_t *l_list_out_prev_item = dap_list_nth(l_list_out_prev_items,
+                                    l_tx_in->header.tx_out_prev_idx);
+                            dap_chain_tx_out_t *l_tx_prev_out =
+                                    l_list_out_prev_item ?
+                                                           (dap_chain_tx_out_t*) l_list_out_prev_item->data :
+                                                           NULL;
+                            // if use src addr
+                            bool l_is_use_src_addr = false;
+                            // find source addrs
+                            dap_string_t *l_src_addr = dap_string_new(NULL);
+                            {
+                                // find IN items in prev datum - for get destination addr
+                                dap_list_t *l_list_in_prev_items = dap_chain_datum_tx_items_get(l_tx_prev,
+                                        TX_ITEM_TYPE_IN, NULL);
+                                dap_list_t *l_list_tmp = l_list_in_prev_items;
+                                while(l_list_tmp) {
+                                    dap_chain_tx_in_t *l_tx_prev_in = l_list_tmp->data;
+                                    dap_chain_hash_fast_t l_tx_prev_prev_hash =
+                                            l_tx_prev_in->header.tx_prev_hash;
+                                    //find prev OUT item
+                                    dap_tx_data_t *l_tx_data_prev_prev = NULL;
+                                    HASH_FIND(hh, l_tx_data_hash, &l_tx_prev_prev_hash,
+                                            sizeof(dap_chain_hash_fast_t), l_tx_data_prev_prev);
+                                    if(l_tx_data_prev_prev) {
+                                        // if use src addr
+                                        if(l_tx_data_prev_prev &&
+                                                !memcmp(&l_tx_data_prev_prev->addr, a_addr,
+                                                        sizeof(dap_chain_addr_t)))
+                                            l_is_use_src_addr = true;
+                                        char *l_str = dap_chain_addr_to_str(&l_tx_data_prev_prev->addr);
+                                        if(l_src_addr->len > 0)
+                                            dap_string_append_printf(l_src_addr, "\n   %s", l_str);
+                                        else
+                                            dap_string_append_printf(l_src_addr, "%s", l_str); // first record
+                                        DAP_DELETE(l_str);
+                                    }
+                                    l_list_tmp = dap_list_next(l_list_tmp);
+                                }
+                            }
+
+                            char *l_dst_to_str =
+                                    (l_tx_prev_out) ? dap_chain_addr_to_str(&l_tx_prev_out->addr) :
+                                    NULL;
+                            // if use dst addr
+                            bool l_is_use_dst_addr = false;
+                            if(!memcmp(&l_tx_prev_out->addr, a_addr, sizeof(dap_chain_addr_t)))
+                                l_is_use_dst_addr = true;
+
+                            l_src_str_is_cur = l_is_use_src_addr;
+                            if(l_src_addr->len <= 1) {
+                                l_src_str =
+                                        (l_tx_data) ? dap_chain_addr_to_str(&l_tx_data->addr) :
+                                        NULL;
+                                if(!memcmp(&l_tx_prev_out->addr, a_addr, sizeof(dap_chain_addr_t)))
+                                    l_src_str_is_cur = true;
+                                dap_string_free(l_src_addr, true);
+                            }
+                            else
+                                l_src_str = dap_string_free(l_src_addr, false);
+                            if(l_is_use_src_addr && !l_is_use_dst_addr) {
+                                dap_string_append_printf(l_str_out,
+                                        "tx hash %s \n %s in send  %lu %s from %s\n to %s\n",
+                                        l_tx_data->tx_hash_str,
+                                        l_time_str ? l_time_str : "",
+                                        l_tx_prev_out->header.value,
+                                        l_tx_data->token_ticker,
+                                        l_src_str ? l_src_str : "",
+                                        l_dst_to_str);
+                            } else if(l_is_use_dst_addr && !l_is_use_src_addr) {
+                                if(!l_src_str_is_cur)
+                                    dap_string_append_printf(l_str_out,
+                                            "tx hash %s \n %s in recv %lu %s from %s\n",
+                                            l_tx_data->tx_hash_str,
+                                            l_time_str ? l_time_str : "",
+                                            l_tx_prev_out->header.value,
+                                            l_tx_data->token_ticker,
+                                            l_src_str ? l_src_str : "");
+                            }
+
+                            DAP_DELETE(l_dst_to_str);
+                            dap_list_free(l_list_out_prev_items);
+                        }
+
+                        // OUT items
+                        dap_list_t *l_records_tmp = l_records_out;
+                        while(l_records_tmp) {
+
+                            const dap_chain_tx_out_t *l_tx_out = (const dap_chain_tx_out_t*) l_records_tmp->data;
+
+                            if(l_tx_data->is_use_all_cur_out
+                                    || !memcmp(&l_tx_out->addr, a_addr, sizeof(dap_chain_addr_t))) {
+
+                                char *l_addr_str = (l_tx_out) ? dap_chain_addr_to_str(&l_tx_out->addr) : NULL;
+
+                                if(!memcmp(&l_tx_out->addr, a_addr, sizeof(dap_chain_addr_t))) {
+                                    if(!l_src_str_is_cur)
+                                        dap_string_append_printf(l_str_out,
+                                                "tx hash %s \n %s recv %lu %s from %s\n",
+                                                l_tx_data->tx_hash_str,
+                                                l_time_str ? l_time_str : "",
+                                                l_tx_out->header.value,
+                                                l_tx_data_prev->token_ticker,
+                                                l_src_str ? l_src_str : "?");
+                                    // break search prev OUT items for IN items
+                                    l_list_in_items2_tmp = NULL;
+                                }
+                                else {
+                                    dap_string_append_printf(l_str_out,
+                                            "tx hash %s \n %s send %lu %s to %s\n",
+                                            l_tx_data->tx_hash_str,
+                                            l_time_str ? l_time_str : "",
+                                            l_tx_out->header.value,
+                                            l_tx_data_prev->token_ticker,
+                                            l_addr_str ? l_addr_str : "");
+                                    l_list_in_items2_tmp = NULL;
+                                }
+                                DAP_DELETE(l_addr_str);
+                            }
+
+                            l_records_tmp = dap_list_next(l_records_tmp);
+                        }
+                        //dap_list_free(l_records_out);
+                        DAP_DELETE(l_src_str);
+
+                    }
+                }
+                l_list_in_items2_tmp = dap_list_next(l_list_in_items2_tmp);
+            }
+//                l_list_in_items_tmp = dap_list_next(l_list_in_items_tmp);
+//            }
+        }
+
+        if(l_list_tx_token)
+            dap_list_free(l_list_tx_token);
+        if(l_list_out_items)
+            dap_list_free(l_list_out_items);
+        if(l_list_in_items)
+            dap_list_free(l_list_in_items);
+        dap_list_free(l_records_out);
+        dap_list_free(l_list_out_info);
+        DAP_DELETE(l_time_str);
+
+        // go to next transaction
+        l_atom = a_chain->callback_atom_iter_get_next(l_atom_iter);
+        l_atom_size = l_atom ? a_chain->callback_atom_get_size(l_atom) : 0;
+    }
+
+    // delete hashes
+    dap_tx_data_t *l_iter_current, *l_item_tmp;
+    HASH_ITER(hh, l_tx_data_hash , l_iter_current, l_item_tmp)
+    {
+        // delete datum
+        DAP_DELETE(l_iter_current->datum);
+        // delete struct
+        DAP_DELETE(l_iter_current);
+        HASH_DEL(l_tx_data_hash, l_iter_current);
+    }
+    // if no history
+    if(!l_str_out->len)
+        dap_string_append(l_str_out, " empty");
+    char *l_ret_str = l_str_out ? dap_string_free(l_str_out, false) : NULL;
+    return l_ret_str;
+}
diff --git a/libdap-chain-net/dap_chain_node_cli_cmd_tx.h b/libdap-chain-net/dap_chain_node_cli_cmd_tx.h
new file mode 100644
index 0000000000000000000000000000000000000000..dfc1e1409f270d365a7602f79cf9f09105e38a2a
--- /dev/null
+++ b/libdap-chain-net/dap_chain_node_cli_cmd_tx.h
@@ -0,0 +1,33 @@
+/*
+ * Authors:
+ * Alexander Lysikov <alexander.lysikov@demlabs.net>
+ * DeM Labs Inc.   https://demlabs.net
+ * Kelvin Project https://github.com/kelvinblockchain
+ * Copyright  (c) 2019
+ * All rights reserved.
+
+ This file is part of DAP (Deus Applications Prototypes) the open source project
+
+ DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ DAP is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with any DAP based project.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "dap_chain.h"
+#include "dap_chain_common.h"
+
+/**
+ *
+ * return history string
+ */
+char* dap_db_history_tx(dap_chain_hash_fast_t* a_tx_hash, dap_chain_t * a_chain);
+char* dap_db_history_addr(dap_chain_addr_t * a_addr, dap_chain_t * a_chain);
diff --git a/libdap-chain-net/dap_chain_node_client.c b/libdap-chain-net/dap_chain_node_client.c
new file mode 100644
index 0000000000000000000000000000000000000000..ce016ffeaafab4a79f24cfa914f0b85ad882f2a1
--- /dev/null
+++ b/libdap-chain-net/dap_chain_node_client.c
@@ -0,0 +1,671 @@
+/*
+ * Authors:
+ * Dmitriy A. Gearasimov <naeper@demlabs.net>
+ * Alexander Lysikov <alexander.lysikov@demlabs.net>
+ * DeM Labs Inc.   https://demlabs.net
+
+ This file is part of DAP (Deus Applications Prototypes) the open source project
+
+ DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ DAP is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with any DAP based project.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <errno.h>
+#include <assert.h>
+#include <string.h>
+
+#ifdef WIN32
+#include <winsock2.h>
+#include <windows.h>
+#include <mswsock.h>
+#include <ws2tcpip.h>
+#include <io.h>
+#include <pthread.h>
+#endif
+
+#include "dap_common.h"
+#include "dap_client.h"
+#include "dap_config.h"
+#include "dap_events.h"
+#include "dap_hash.h"
+//#include "dap_http_client_simple.h"
+#include "dap_client_pvt.h"
+#include "dap_chain_global_db_remote.h"
+#include "dap_chain_global_db_hist.h"
+#include "dap_stream_ch_pkt.h"
+#include "dap_stream_ch_chain.h"
+#include "dap_stream_ch_chain_pkt.h"
+#include "dap_stream_ch_chain_net.h"
+#include "dap_stream_ch_chain_net_pkt.h"
+#include "dap_stream_pkt.h"
+
+//#include "dap_chain_common.h"
+#include "dap_chain_node_client.h"
+
+#define LOG_TAG "dap_chain_node_client"
+
+
+//static int listen_port_tcp = 8079;
+
+static void s_stage_connected_callback(dap_client_t *a_client, void *a_arg);
+static void s_ch_chain_callback_notify_packet_out(dap_stream_ch_chain_t*, uint8_t a_pkt_type,
+        dap_stream_ch_chain_pkt_t *a_pkt, size_t a_pkt_data_size,
+        void * a_arg);
+static void s_ch_chain_callback_notify_packet_in(dap_stream_ch_chain_t* a_ch_chain, uint8_t a_pkt_type,
+        dap_stream_ch_chain_pkt_t *a_pkt, size_t a_pkt_data_size,
+        void * a_arg);
+
+/**
+ * @brief dap_chain_node_client_init
+ * @return
+ */
+int dap_chain_node_client_init(void)
+{
+    return 0;
+}
+
+/**
+ * @brief dap_chain_node_client_deinit
+ */
+void dap_chain_node_client_deinit()
+{
+    //dap_http_client_simple_deinit();
+    dap_client_deinit();
+}
+
+/**
+ * @brief stage_status_callback
+ * @param a_client
+ * @param a_arg
+ */
+static void s_stage_status_callback(dap_client_t *a_client, void *a_arg)
+{
+    (void) a_client;
+    (void) a_arg;
+
+    //printf("* stage_status_callback client=%x data=%x\n", a_client, a_arg);
+}
+
+/**
+ * @brief s_stage_status_error_callback
+ * @param a_client
+ * @param a_arg
+ */
+static void s_stage_status_error_callback(dap_client_t *a_client, void *a_arg)
+{
+    dap_chain_node_client_t *l_node_client = DAP_CHAIN_NODE_CLIENT(a_client);
+    if(!l_node_client)
+        return;
+    // check for last attempt
+    bool l_is_last_attempt = a_arg ? true : false;
+    if(l_is_last_attempt){
+        pthread_mutex_lock(&l_node_client->wait_mutex);
+        l_node_client->state = NODE_CLIENT_STATE_DISCONNECTED;
+        pthread_mutex_unlock(&l_node_client->wait_mutex);
+#ifndef _WIN32
+        pthread_cond_signal(&l_node_client->wait_cond);
+#else
+        SetEvent( l_node_client->wait_cond );
+#endif
+    }
+
+    if(l_node_client && l_node_client->keep_connection &&
+            ((dap_client_get_stage(a_client) != STAGE_STREAM_STREAMING) ||
+                    (dap_client_get_stage_status(a_client) == STAGE_STATUS_ERROR))) {
+        log_it(L_NOTICE,"Some errors happends, current state is %s but we need to return back to STAGE_STREAM_STREAMING",
+                dap_client_get_stage_str(a_client) );
+
+        log_it(L_DEBUG, "Wakeup all who waits");
+        pthread_mutex_lock(&l_node_client->wait_mutex);
+        l_node_client->state = NODE_CLIENT_STATE_ERROR;
+        pthread_mutex_unlock(&l_node_client->wait_mutex);
+
+#ifndef _WIN32
+        pthread_cond_signal(&l_node_client->wait_cond);
+#else
+        SetEvent( l_node_client->wait_cond );
+#endif
+        //dap_client_go_stage( a_client , STAGE_STREAM_STREAMING, s_stage_end_callback );
+    }
+    //printf("* tage_status_error_callback client=%x data=%x\n", a_client, a_arg);
+}
+
+/**
+ * @brief a_stage_end_callback
+ * @param a_client
+ * @param a_arg
+ */
+static void s_stage_connected_callback(dap_client_t *a_client, void *a_arg)
+{
+    dap_chain_node_client_t *l_node_client = a_client->_inheritor;
+    //assert(l_node_client);
+    if(l_node_client) {
+        log_it(L_NOTICE, "Stream connection with node " NODE_ADDR_FP_STR " established",
+                NODE_ADDR_FP_ARGS_S( l_node_client->remote_node_addr));
+        pthread_mutex_lock(&l_node_client->wait_mutex);
+        l_node_client->state = NODE_CLIENT_STATE_CONNECTED;
+        // find current channel code
+        dap_client_pvt_t * l_client_internal = DAP_CLIENT_PVT(a_client);
+        dap_stream_ch_t * l_ch = NULL;
+        if(l_client_internal && l_client_internal->active_channels)
+            l_ch = dap_client_get_stream_ch(a_client, l_client_internal->active_channels[0]);
+        //dap_stream_ch_t * l_ch = dap_client_get_stream_ch(a_client, dap_stream_ch_chain_get_id());
+        if(l_ch) {
+            dap_stream_ch_chain_t * l_ch_chain = DAP_STREAM_CH_CHAIN(l_ch);
+            l_ch_chain->callback_notify_packet_out = s_ch_chain_callback_notify_packet_out;
+            l_ch_chain->callback_notify_packet_in = s_ch_chain_callback_notify_packet_in;
+            l_ch_chain->callback_notify_arg = l_node_client;
+        } else {
+            log_it(L_WARNING, "No ch_chain channel, can't init notify callback for pkt type CH_CHAIN");
+        }
+
+        pthread_mutex_unlock(&l_node_client->wait_mutex);
+        if(l_node_client->callback_connected)
+            l_node_client->callback_connected(l_node_client, a_arg);
+        l_node_client->keep_connection = true;
+        log_it(L_DEBUG, "Wakeup all who waits");
+
+#ifndef _WIN32
+        pthread_cond_signal(&l_node_client->wait_cond);
+#else
+        SetEvent( l_node_client->wait_cond );
+#endif
+
+    }
+}
+
+/**
+ * @brief s_ch_chain_callback_notify_packet_in2 - for dap_stream_ch_chain_net
+ * @param a_ch_chain_net
+ * @param a_pkt_type
+ * @param a_pkt_net
+ * @param a_pkt_data_size
+ * @param a_arg
+ */
+static void s_ch_chain_callback_notify_packet_in2(dap_stream_ch_chain_net_t* a_ch_chain_net, uint8_t a_pkt_type,
+        dap_stream_ch_chain_net_pkt_t *a_pkt_net, size_t a_pkt_net_data_size, void * a_arg)
+{
+    dap_chain_node_client_t * l_node_client = (dap_chain_node_client_t *) a_arg;
+    switch (a_pkt_type) {
+    // get new generated current node address
+    case DAP_STREAM_CH_CHAIN_NET_PKT_TYPE_NODE_ADDR_LEASE: {
+        if(a_pkt_net_data_size == sizeof(dap_chain_node_addr_t)) {
+            dap_chain_node_addr_t *l_addr = (dap_chain_node_addr_t *) a_pkt_net->data;
+            memcpy(&l_node_client->cur_node_addr, l_addr, sizeof(dap_chain_node_addr_t));
+        }
+        pthread_mutex_lock(&l_node_client->wait_mutex);
+        l_node_client->state = NODE_CLIENT_STATE_NODE_ADDR_LEASED;
+        pthread_mutex_unlock(&l_node_client->wait_mutex);
+#ifndef _WIN32
+        pthread_cond_signal(&l_node_client->wait_cond);
+#else
+                    SetEvent( l_node_client->wait_cond );
+        #endif
+        break;
+    }
+    // get remote node address
+    case DAP_STREAM_CH_CHAIN_NET_PKT_TYPE_NODE_ADDR: {
+
+        if(a_pkt_net_data_size == sizeof(dap_chain_node_addr_t)) {
+            dap_chain_node_addr_t *l_addr = (dap_chain_node_addr_t *) a_pkt_net->data;
+            memcpy(&l_node_client->remote_node_addr, l_addr, sizeof(dap_chain_node_addr_t));
+        }
+        pthread_mutex_lock(&l_node_client->wait_mutex);
+        l_node_client->state = NODE_CLIENT_STATE_GET_NODE_ADDR;
+        pthread_mutex_unlock(&l_node_client->wait_mutex);
+#ifndef _WIN32
+        pthread_cond_signal(&l_node_client->wait_cond);
+#else
+            SetEvent( l_node_client->wait_cond );
+#endif
+        break;
+    }
+    }
+}
+
+/**
+ * @brief s_ch_chain_callback_notify_packet_in - for dap_stream_ch_chain
+ * @param a_ch_chain
+ * @param a_pkt_type
+ * @param a_pkt
+ * @param a_pkt_data_size
+ * @param a_arg
+ */
+static void s_ch_chain_callback_notify_packet_in(dap_stream_ch_chain_t* a_ch_chain, uint8_t a_pkt_type,
+        dap_stream_ch_chain_pkt_t *a_pkt, size_t a_pkt_data_size,
+        void * a_arg)
+{
+    dap_chain_node_client_t * l_node_client = (dap_chain_node_client_t *) a_arg;
+    switch (a_pkt_type) {
+    case DAP_STREAM_CH_CHAIN_NET_PKT_TYPE_ERROR:
+        pthread_mutex_lock(&l_node_client->wait_mutex);
+        l_node_client->state = NODE_CLIENT_STATE_ERROR;
+        dap_snprintf(l_node_client->last_error, sizeof(l_node_client->last_error),
+                "%s", (char*) a_pkt->data);
+        log_it(L_WARNING, "Received packet DAP_STREAM_CH_CHAIN_NET_PKT_TYPE_ERROR with error \"%s\"",
+                l_node_client->last_error);
+        pthread_mutex_unlock(&l_node_client->wait_mutex);
+
+#ifndef _WIN32
+        pthread_cond_signal(&l_node_client->wait_cond);
+#else
+            SetEvent( l_node_client->wait_cond );
+#endif
+
+    case DAP_STREAM_CH_CHAIN_PKT_TYPE_SYNCED_ALL:
+        case DAP_STREAM_CH_CHAIN_PKT_TYPE_SYNCED_GLOBAL_DB:
+        case DAP_STREAM_CH_CHAIN_PKT_TYPE_SYNCED_CHAINS: {
+        dap_stream_ch_chain_sync_request_t * l_request = NULL;
+        if(a_pkt_data_size == sizeof(*l_request))
+            l_request = (dap_stream_ch_chain_sync_request_t*) a_pkt->data;
+
+        if(l_request) {
+            uint64_t l_id_last_here = 1;
+            // for sync chain not used time
+            //if(a_pkt_type != DAP_STREAM_CH_CHAIN_PKT_TYPE_SYNCED_CHAINS)
+            //    l_id_last_here =(uint64_t) dap_db_log_get_last_id();
+            if(1) {//if(l_request->id_start < l_id_last_here) {
+                log_it(L_INFO, "Remote is synced but we have updates for it");
+                bool l_is_sync = true;
+                // Get log diff
+                if(a_pkt_type == DAP_STREAM_CH_CHAIN_PKT_TYPE_SYNCED_GLOBAL_DB) {
+                    a_ch_chain->request_last_ts = dap_db_log_get_last_id();
+                    uint64_t l_start_item = l_request->id_start;
+                    dap_db_log_list_t *l_db_log = NULL;
+                    // If the current global_db has been truncated, but the remote node has not known this
+                    uint64_t l_last_id = dap_db_log_get_last_id();
+                    if(l_request->id_start > a_ch_chain->request_last_ts){
+                        dap_chain_net_t *l_net = dap_chain_net_by_id(a_pkt->hdr.net_id);
+                        dap_list_t *l_add_groups = dap_chain_net_get_add_gdb_group(l_net, a_ch_chain->request.node_addr);
+                        l_db_log = dap_db_log_list_start(l_start_item + 1, l_add_groups);
+                        if(!l_db_log)
+                            l_start_item = 0;
+                    }
+                    //dap_list_t *l_list = dap_db_log_get_list(l_request->id_start + 1);
+                    if(!l_db_log){
+                        dap_chain_net_t *l_net = dap_chain_net_by_id(a_pkt->hdr.net_id);
+                        dap_list_t *l_add_groups = dap_chain_net_get_add_gdb_group(l_net, a_ch_chain->request.node_addr);
+                        l_db_log = dap_db_log_list_start(l_start_item + 1, l_add_groups);
+                    }
+                    if(l_db_log) {
+                        // Add it to outgoing list
+                        //l_list->prev = a_ch_chain->request_global_db_trs;
+                        a_ch_chain->request_global_db_trs = l_db_log;//l_list;
+                    }
+                    else
+                        l_is_sync = false;
+                }
+                if(l_is_sync) {
+                    a_ch_chain->request_net_id.uint64 = a_pkt->hdr.net_id.uint64;
+                    a_ch_chain->request_cell_id.uint64 = a_pkt->hdr.cell_id.uint64;
+                    a_ch_chain->request_chain_id.uint64 = a_pkt->hdr.chain_id.uint64;
+                    a_ch_chain->state = dap_stream_ch_chain_pkt_type_to_dap_stream_ch_chain_state(a_pkt_type);//CHAIN_STATE_SYNC_CHAINS;//GLOBAL_DB;
+
+                    // type of first packet
+                    uint8_t l_type =
+                            (a_pkt_type != DAP_STREAM_CH_CHAIN_PKT_TYPE_SYNCED_CHAINS) ?
+                                    DAP_STREAM_CH_CHAIN_PKT_TYPE_FIRST_GLOBAL_DB :
+                                    DAP_STREAM_CH_CHAIN_PKT_TYPE_FIRST_CHAIN;
+                    if(l_type == DAP_STREAM_CH_CHAIN_PKT_TYPE_FIRST_CHAIN)
+                    {
+                        dap_chain_t * l_chain = dap_chain_find_by_id(a_pkt->hdr.net_id, a_pkt->hdr.chain_id);
+                        dap_chain_atom_iter_t* l_iter = l_chain ? l_chain->callback_atom_iter_create(l_chain) : NULL;
+                        //a_ch_chain->request_atom_iter = l_iter;
+
+                        dap_chain_atom_ptr_t * l_lasts = NULL;
+                        size_t l_lasts_size = 0;
+                        l_lasts = l_chain->callback_atom_iter_get_lasts(l_iter, &l_lasts_size);
+                        for(size_t i = 0; i < l_lasts_size; i++) {
+                            dap_chain_atom_item_t * l_item = NULL;
+                            dap_chain_hash_fast_t l_atom_hash;
+                            dap_hash_fast(l_lasts[i], l_chain->callback_atom_get_size(l_lasts[i]), &l_atom_hash);
+                            HASH_FIND(hh, a_ch_chain->request_atoms_lasts, &l_atom_hash, sizeof(l_atom_hash), l_item);
+                            if(l_item == NULL) { // Not found, add new lasts
+                                l_item = DAP_NEW_Z(dap_chain_atom_item_t);
+                                l_item->atom = l_lasts[i];
+                                memcpy(&l_item->atom_hash, &l_atom_hash, sizeof(l_atom_hash));
+                                HASH_ADD(hh, a_ch_chain->request_atoms_lasts, atom_hash, sizeof(l_atom_hash), l_item);
+                            }
+                            //else
+                            //    DAP_DELETE(l_lasts[i]);
+                        }
+                        DAP_DELETE(l_lasts);
+                        DAP_DELETE(l_iter);
+                    }
+                    dap_chain_node_addr_t l_node_addr = { 0 };
+                    dap_chain_net_t *l_net = dap_chain_net_by_id(a_ch_chain->request_net_id);
+                    l_node_addr.uint64 = l_net ? dap_db_get_cur_node_addr(l_net->pub.name) : 0;
+                    dap_stream_ch_chain_pkt_write(a_ch_chain->ch, l_type,
+                            a_ch_chain->request_net_id, a_ch_chain->request_chain_id,
+                            a_ch_chain->request_cell_id, &l_node_addr, sizeof(dap_chain_node_addr_t));
+
+                    log_it(L_INFO, "Sync for remote tr type=%d", a_pkt_type);//_count=%d", dap_list_length(l_list));
+                    dap_stream_ch_set_ready_to_write(a_ch_chain->ch, true);
+                }
+                else {
+                    log_it(L_INFO, "Remote node has lastes timestamp for us type=%d", a_pkt_type);
+                    pthread_mutex_lock(&l_node_client->wait_mutex);
+                    l_node_client->state = NODE_CLIENT_STATE_SYNCED;
+                    pthread_mutex_unlock(&l_node_client->wait_mutex);
+#ifndef _WIN32
+                    pthread_cond_signal(&l_node_client->wait_cond);
+#else
+                    SetEvent( l_node_client->wait_cond );
+#endif
+
+                }
+            }
+        } else {
+            log_it(L_INFO, "Sync notify without request to sync back, stay in SYNCED state");
+            pthread_mutex_lock(&l_node_client->wait_mutex);
+            l_node_client->state = NODE_CLIENT_STATE_SYNCED;
+            pthread_mutex_unlock(&l_node_client->wait_mutex);
+#ifndef _WIN32
+            pthread_cond_signal(&l_node_client->wait_cond);
+#else
+                SetEvent( l_node_client->wait_cond );
+#endif
+        }
+
+    }
+    default: {
+    }
+    }
+}
+
+/**
+ * @brief s_ch_chain_callback_notify_packet_in
+ * @param a_ch_chain
+ * @param a_pkt_type
+ * @param a_pkt
+ * @param a_pkt_data_size
+ * @param a_arg
+ */
+static void s_ch_chain_callback_notify_packet_out(dap_stream_ch_chain_t* a_ch_chain, uint8_t a_pkt_type,
+        dap_stream_ch_chain_pkt_t *a_pkt, size_t a_pkt_data_size,
+        void * a_arg)
+{
+    (void) a_pkt;
+    (void) a_pkt_data_size;
+    (void) a_ch_chain;
+    dap_chain_node_client_t * l_node_client = (dap_chain_node_client_t *) a_arg;
+    switch (a_pkt_type) {
+    case DAP_STREAM_CH_CHAIN_PKT_TYPE_SYNCED_ALL:
+        case DAP_STREAM_CH_CHAIN_PKT_TYPE_SYNCED_GLOBAL_DB:
+        case DAP_STREAM_CH_CHAIN_PKT_TYPE_SYNCED_CHAINS: {
+        pthread_mutex_lock(&l_node_client->wait_mutex);
+        l_node_client->state = NODE_CLIENT_STATE_SYNCED;
+        pthread_mutex_unlock(&l_node_client->wait_mutex);
+#ifndef _WIN32
+        pthread_cond_signal(&l_node_client->wait_cond);
+#else
+            SetEvent( l_node_client->wait_cond );
+#endif
+    }
+        break;
+    default: {
+    }
+    }
+}
+
+/**
+ * Create connection to server
+ *
+ * return a connection handle, or NULL, if an error
+ */
+dap_chain_node_client_t* dap_chain_client_connect(dap_chain_node_info_t *a_node_info, dap_client_stage_t a_stage_target,
+        const char *a_active_channels)
+{
+    if(!a_node_info) {
+        log_it(L_ERROR, "Can't connect to the node: null object node_info");
+        return NULL;
+    }
+    dap_chain_node_client_t *l_node_client = DAP_NEW_Z(dap_chain_node_client_t);
+    l_node_client->state = NODE_CLIENT_STATE_DISCONNECTED;
+
+#ifndef _WIN32
+    pthread_condattr_t attr;
+    pthread_condattr_init(&attr);
+    pthread_condattr_setclock(&attr, CLOCK_MONOTONIC);
+    pthread_cond_init(&l_node_client->wait_cond, &attr);
+#else
+    l_node_client->wait_cond = CreateEventA( NULL, FALSE, FALSE, NULL );
+#endif
+
+    pthread_mutex_init(&l_node_client->wait_mutex, NULL);
+    l_node_client->events = NULL; //dap_events_new();
+    l_node_client->client = dap_client_new(l_node_client->events, s_stage_status_callback,
+            s_stage_status_error_callback);
+    l_node_client->client->_inheritor = l_node_client;
+    l_node_client->remote_node_addr.uint64 = a_node_info->hdr.address.uint64;
+    dap_client_set_active_channels(l_node_client->client, a_active_channels);
+
+    int hostlen = 128;
+    char host[hostlen];
+    if(a_node_info->hdr.ext_addr_v4.s_addr)
+    {
+        struct sockaddr_in sa4 = { .sin_family = AF_INET, .sin_addr = a_node_info->hdr.ext_addr_v4 };
+        inet_ntop(AF_INET, &(((struct sockaddr_in *) &sa4)->sin_addr), host, hostlen);
+    }
+    else
+    {
+        struct sockaddr_in6 sa6 = { .sin6_family = AF_INET6, .sin6_addr = a_node_info->hdr.ext_addr_v6 };
+        inet_ntop(AF_INET6, &(((struct sockaddr_in6 *) &sa6)->sin6_addr), host, hostlen);
+    }
+    // address not defined
+    if(!strcmp(host, "::")) {
+        dap_chain_node_client_close(l_node_client);
+        return NULL;
+    }
+    dap_client_set_uplink(l_node_client->client, strdup(host), a_node_info->hdr.ext_port);
+//    dap_client_stage_t a_stage_target = STAGE_ENC_INIT;
+//    dap_client_stage_t l_stage_target = STAGE_STREAM_STREAMING;
+
+    l_node_client->state = NODE_CLIENT_STATE_CONNECT;
+    // ref pvt client
+    //dap_client_pvt_ref(DAP_CLIENT_PVT(l_node_client->client));
+    // Handshake & connect
+    dap_client_go_stage(l_node_client->client, a_stage_target, s_stage_connected_callback);
+    return l_node_client;
+}
+
+/**
+ * Create connection to server
+ *
+ * return a connection handle, or NULL, if an error
+ */
+dap_chain_node_client_t* dap_chain_node_client_connect(dap_chain_node_info_t *a_node_info)
+{
+    dap_client_stage_t l_stage_target = STAGE_STREAM_STREAMING;
+    const char *l_active_channels = "CN";
+    return dap_chain_client_connect(a_node_info, l_stage_target, l_active_channels);
+}
+
+/**
+ * Close connection to server, delete chain_node_client_t *client
+ */
+void dap_chain_node_client_close(dap_chain_node_client_t *a_client)
+{
+    if (a_client && a_client->client) { // block tryes to close twice
+        // clean client
+        dap_client_delete(a_client->client);
+#ifndef _WIN32
+        pthread_cond_destroy(&a_client->wait_cond);
+#else
+        CloseHandle( a_client->wait_cond );
+#endif
+        pthread_mutex_destroy(&a_client->wait_mutex);
+        DAP_DELETE(a_client);
+    }
+}
+
+/**
+ * Send stream request to server
+ */
+int dap_chain_node_client_send_ch_pkt(dap_chain_node_client_t *a_client, uint8_t a_ch_id, uint8_t a_type,
+        const void *a_pkt_data, size_t a_pkt_data_size)
+{
+    if(!a_client || a_client->state < NODE_CLIENT_STATE_CONNECTED)
+        return -1;
+
+//    dap_stream_t *l_stream = dap_client_get_stream(a_client->client);
+    dap_stream_ch_t * l_ch = dap_client_get_stream_ch(a_client->client, a_ch_id);
+    if(l_ch) {
+//        dap_stream_ch_chain_net_t * l_ch_chain = DAP_STREAM_CH_CHAIN_NET(l_ch);
+
+        dap_stream_ch_pkt_write(l_ch, a_type, a_pkt_data, a_pkt_data_size);
+        dap_stream_ch_set_ready_to_write(l_ch, true);
+        return 0;
+    } else
+        return -1;
+}
+
+/**
+ * wait for the complete of request
+ *
+ * timeout_ms timeout in milliseconds
+ * waited_state state which we will wait, sample NODE_CLIENT_STATE_CONNECT or NODE_CLIENT_STATE_SENDED
+ * return -2 false, -1 timeout, 0 end of connection or sending data
+ */
+int dap_chain_node_client_wait(dap_chain_node_client_t *a_client, int a_waited_state, int a_timeout_ms)
+{
+    int ret = -1;
+    if(!a_client)
+        return -3;
+
+    pthread_mutex_lock(&a_client->wait_mutex);
+    // have waited
+    if(a_client->state == a_waited_state) {
+        pthread_mutex_unlock(&a_client->wait_mutex);
+        return 0;
+    }
+    if(a_client->state == NODE_CLIENT_STATE_DISCONNECTED) {
+        pthread_mutex_unlock(&a_client->wait_mutex);
+        return -2;
+    }
+
+#ifndef _WIN32
+    // prepare for signal waiting
+    struct timespec to;
+    clock_gettime( CLOCK_MONOTONIC, &to);
+    int64_t nsec_new = to.tv_nsec + a_timeout_ms * 1000000ll;
+    // if the new number of nanoseconds is more than a second
+
+    if(nsec_new > (long) 1e9) {
+        to.tv_sec += nsec_new / (long) 1e9;
+        to.tv_nsec = nsec_new % (long) 1e9;
+    }
+    else
+        to.tv_nsec = (long) nsec_new;
+#else
+    pthread_mutex_unlock( &a_client->wait_mutex );
+#endif
+
+    // signal waiting
+    do {
+
+#ifndef _WIN32
+        int wait = pthread_cond_timedwait(&a_client->wait_cond, &a_client->wait_mutex, &to);
+        if(wait == 0 && (
+                a_client->state == a_waited_state ||
+                        (a_client->state == NODE_CLIENT_STATE_ERROR || a_client->state == NODE_CLIENT_STATE_DISCONNECTED))
+                ) {
+            ret = a_client->state == a_waited_state ? 0 : -2;
+            break;
+        }
+        else if(wait == ETIMEDOUT) { // 110 260
+            ret = -1;
+            break;
+        }
+#else
+        int wait = WaitForSingleObject( a_client->wait_cond, (uint32_t)a_timeout_ms );
+        pthread_mutex_lock( &a_client->wait_mutex );
+
+        if ( wait == WAIT_OBJECT_0 && (
+                    a_client->state == a_waited_state ||
+                    a_client->state == NODE_CLIENT_STATE_ERROR || a_client->state == NODE_CLIENT_STATE_DISCONNECTED
+          ) {
+            ret = a_client->state == a_waited_state ? 0 : -2;
+            break;
+        }
+        else if ( wait == WAIT_TIMEOUT || wait == WAIT_FAILED ) {
+            ret = -1;
+            break;
+        }
+#endif
+
+    } while(1);
+
+    pthread_mutex_unlock(&a_client->wait_mutex);
+    return ret;
+}
+
+int dap_chain_node_client_set_callbacks(dap_client_t *a_client, uint8_t a_ch_id)
+{
+    int l_ret = -1;
+    dap_chain_node_client_t *l_node_client = a_client->_inheritor;
+    if(l_node_client) {
+        pthread_mutex_lock(&l_node_client->wait_mutex);
+        // find current channel code
+        dap_client_pvt_t * l_client_internal = DAP_CLIENT_PVT(a_client);
+        dap_stream_ch_t * l_ch = NULL;
+        if(l_client_internal)
+            l_ch = dap_client_get_stream_ch(a_client, a_ch_id);
+        if(l_ch) {
+            if(a_ch_id == dap_stream_ch_chain_get_id()) {
+                dap_stream_ch_chain_t * l_ch_chain = DAP_STREAM_CH_CHAIN(l_ch);
+                l_ch_chain->callback_notify_packet_out = s_ch_chain_callback_notify_packet_out;
+                l_ch_chain->callback_notify_packet_in = s_ch_chain_callback_notify_packet_in;
+                l_ch_chain->callback_notify_arg = l_node_client;
+            }
+            if(a_ch_id == dap_stream_ch_chain_net_get_id()) {
+                dap_stream_ch_chain_net_t *l_ch_chain = DAP_STREAM_CH_CHAIN_NET(l_ch);
+                l_ch_chain->notify_callback = s_ch_chain_callback_notify_packet_in2;
+                l_ch_chain->notify_callback_arg = l_node_client;
+            }
+            l_ret = 0;
+        } else {
+        }
+        pthread_mutex_unlock(&l_node_client->wait_mutex);
+    }
+    return l_ret;
+}
+
+/*static void nodelist_response_callback(dap_client_t *a_client, void *data, size_t data_len)
+{
+}
+
+static void nodelist_response_error_callback(dap_client_t *a_client, int a_err)
+{
+}*/
+
+/**
+ * Send nodelist request to server
+ */
+int dap_chain_node_client_send_nodelist_req(dap_chain_node_client_t *a_client)
+{
+    if(!a_client || !a_client->client || a_client->state < NODE_CLIENT_STATE_CONNECTED)
+        return -1;
+    //dap_client_pvt_t * l_client_pvt = DAP_CLIENT_PVT(a_client->client);
+
+    //TODO send request to get nodelist
+    //dap_client_request_enc(a_client->client, DAP_UPLINK_PATH_NODE_LIST, "", "", "", 0,
+    //        nodelist_response_callback, nodelist_response_error_callback);
+    return 1;
+}
diff --git a/libdap-chain-net/dap_chain_node_client.h b/libdap-chain-net/dap_chain_node_client.h
new file mode 100644
index 0000000000000000000000000000000000000000..4a5eb2e307768a133a46050a280d871b4bff22e2
--- /dev/null
+++ b/libdap-chain-net/dap_chain_node_client.h
@@ -0,0 +1,117 @@
+/*
+ * Authors:
+ * Dmitriy A. Gearasimov <naeper@demlabs.net>
+ * Alexander Lysikov <alexander.lysikov@demlabs.net>
+ * DeM Labs Inc.   https://demlabs.net
+
+ This file is part of DAP (Deus Applications Prototypes) the open source project
+
+ DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ DAP is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with any DAP based project.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <pthread.h>
+#include <stdbool.h>
+
+#include "uthash.h"
+#include "dap_client.h"
+#include "dap_chain_node.h"
+
+// connection states
+typedef enum dap_chain_node_client_state {
+    NODE_CLIENT_STATE_ERROR = -1,
+    NODE_CLIENT_STATE_DISCONNECTED = 0,
+    NODE_CLIENT_STATE_GET_NODE_ADDR = 1,
+    NODE_CLIENT_STATE_NODE_ADDR_LEASED = 2,
+    NODE_CLIENT_STATE_PING = 3,
+    NODE_CLIENT_STATE_PONG = 4,
+    NODE_CLIENT_STATE_CONNECT = 5,
+    NODE_CLIENT_STATE_CONNECTED = 100,
+    //NODE_CLIENT_STATE_SEND,
+    //NODE_CLIENT_STATE_SENDED,
+    NODE_CLIENT_STATE_SYNC_GDB = 101,
+    NODE_CLIENT_STATE_SYNC_CHAINS = 102,
+    NODE_CLIENT_STATE_SYNCED = 103,
+} dap_chain_node_client_state_t;
+
+typedef struct dap_chain_node_client dap_chain_node_client_t;
+
+typedef void (*dap_chain_node_client_callback_t)(dap_chain_node_client_t *, void*);
+
+// state for a client connection
+typedef struct dap_chain_node_client {
+    dap_chain_node_client_state_t state;
+    dap_chain_cell_id_t cell_id;
+    dap_client_t *client;
+    dap_events_t *events;
+    char last_error[128];
+
+    dap_chain_node_client_callback_t callback_connected;
+    #ifndef _WIN32
+    pthread_cond_t wait_cond;
+    #else
+    HANDLE wait_cond;
+#endif
+    pthread_mutex_t wait_mutex;
+
+    // For hash indexing
+    UT_hash_handle hh;
+    dap_chain_node_addr_t cur_node_addr;
+    dap_chain_node_addr_t remote_node_addr;
+    struct in_addr remote_ipv4;
+    struct in6_addr remote_ipv6;
+
+    bool keep_connection;
+} dap_chain_node_client_t;
+#define DAP_CHAIN_NODE_CLIENT(a) (a ? (dap_chain_node_client_t *) (a)->_inheritor : NULL)
+
+int dap_chain_node_client_init(void);
+
+void dap_chain_node_client_deinit(void);
+
+dap_chain_node_client_t* dap_chain_client_connect(dap_chain_node_info_t *a_node_info, dap_client_stage_t a_stage_target,
+        const char *a_active_channels);
+/**
+ * Create handshake to server
+ *
+ * return a connection handle, or NULL, if an error
+ */
+dap_chain_node_client_t* dap_chain_node_client_connect(dap_chain_node_info_t *node_info);
+
+/**
+ * Close connection to server, delete chain_node_client_t *client
+ */
+void dap_chain_node_client_close(dap_chain_node_client_t *client);
+
+/**
+ * Send stream request to server
+ */
+int dap_chain_node_client_send_ch_pkt(dap_chain_node_client_t *a_client, uint8_t a_ch_id, uint8_t a_type,
+        const void *a_buf, size_t a_buf_size);
+
+/**
+ * wait for the complete of request
+ *
+ * timeout_ms timeout in milliseconds
+ * waited_state state which we will wait, sample NODE_CLIENT_STATE_CONNECT or NODE_CLIENT_STATE_SENDED
+ * return -1 false, 0 timeout, 1 end of connection or sending data
+ */
+int dap_chain_node_client_wait(dap_chain_node_client_t *a_client, int a_waited_state, int a_timeout_ms);
+
+int dap_chain_node_client_set_callbacks(dap_client_t *a_client, uint8_t a_ch_id);
+
+int dap_chain_node_client_send_nodelist_req(dap_chain_node_client_t *a_client);
+
+
diff --git a/libdap-chain-net/dap_chain_node_ping.c b/libdap-chain-net/dap_chain_node_ping.c
new file mode 100644
index 0000000000000000000000000000000000000000..660dfa97892d7da1a0588ebb8fb4299e35daa02a
--- /dev/null
+++ b/libdap-chain-net/dap_chain_node_ping.c
@@ -0,0 +1,320 @@
+/*
+ * Authors:
+ * Alexander Lysikov <alexander.lysikov@demlabs.net>
+ * DeM Labs Inc.   https://demlabs.net
+
+ This file is part of DAP (Deus Applications Prototypes) the open source project
+
+ DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ DAP is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with any DAP based project.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <string.h>
+//#include <sys/socket.h>
+#include <time.h>
+#include <errno.h>
+#include <pthread.h>
+
+#include "dap_common.h"
+#include "dap_client.h"
+#include "dap_strfuncs.h"
+#include "dap_list.h"
+#include "dap_chain_common.h"
+#include "dap_chain_node.h"
+#include "dap_chain_node_ping.h"
+
+/*
+ #include <stdlib.h>
+ #include <stdio.h>
+ #include <stddef.h>
+ #include <stdint.h>
+ #include <string.h>
+ #include <stdbool.h>
+ #include <assert.h>
+ #include <ctype.h>
+ #include <dirent.h>
+ */
+
+#ifdef WIN32
+#undef _WIN32_WINNT
+#define _WIN32_WINNT 0x0600
+#include <winsock2.h>
+#include <windows.h>
+#include <mswsock.h>
+#include <ws2tcpip.h>
+#include <io.h>
+#include <wepoll.h>
+#else
+#include <signal.h>
+#endif
+#include <pthread.h>
+
+#include "iputils/iputils.h"
+
+#include "dap_common.h"
+//#include "dap_client.h"
+#include "dap_strfuncs.h"
+#include "dap_chain_node_cli.h"
+#include "dap_chain_node_ping.h"
+
+#define LOG_TAG "chain_node_ping"
+
+static void* node_ping_proc(void *a_arg)
+{
+    struct in_addr l_addr = { 0 };
+    int l_port = 0;
+    int l_count;
+    if(!a_arg)
+        return NULL ;
+    memcpy(&l_count, a_arg, sizeof(int));
+    memcpy(&l_port, (a_arg + sizeof(int)), sizeof(int));
+    memcpy(&l_addr, (a_arg + 2 * sizeof(int)), sizeof(struct in_addr));
+    DAP_DELETE(a_arg);
+
+    char *host4 = DAP_NEW_SIZE(char, INET_ADDRSTRLEN);
+    struct sockaddr_in sa4 = { .sin_family = AF_INET, .sin_addr = l_addr };
+    const char* str_ip4 = inet_ntop(AF_INET, &(((struct sockaddr_in *) &sa4)->sin_addr), host4, INET_ADDRSTRLEN);
+    if(!str_ip4)
+        return NULL ;
+    //printf(" %s %d ping start\n", str_ip4, l_count);
+    /*
+     // send ping
+     ping_handle_t *l_ping_handle = ping_handle_create();
+     //iputils_set_verbose();
+     int res = ping_util(l_ping_handle, str_ip4, l_count);
+     DAP_DELETE(l_ping_handle);
+     printf(" %s %d ping=%d us\n",str_ip4,l_count,res );
+     DAP_DELETE(host4);
+     */
+
+    // instead of ping to connect with server and send/recv header
+    long res = -1;
+    {
+        struct timespec l_time_start, l_time_stop;
+        struct sockaddr_in l_remote_addr = { 0 };
+        //memset(&l_remote_addr, 0, sizeof(l_remote_addr));
+        l_remote_addr.sin_family = AF_INET;
+        l_remote_addr.sin_port = htons(l_port);
+        l_remote_addr.sin_addr = l_addr;
+
+        SOCKET l_socket = socket( PF_INET, SOCK_STREAM, 0);
+        if(l_socket == INVALID_SOCKET) {
+            log_it(L_ERROR, "Can't create socket");
+            return (void*) -1;
+        }
+        clock_gettime(CLOCK_MONOTONIC, &l_time_start);
+
+        if(connect(l_socket, (struct sockaddr *) &l_remote_addr, sizeof(struct sockaddr_in)) != SOCKET_ERROR) {
+            size_t l_buf_size = 1024;
+            uint8_t l_buf[l_buf_size];
+
+            const char* str_ip4 = inet_ntop(AF_INET, &(((struct sockaddr_in *) &sa4)->sin_addr), host4,
+            INET_ADDRSTRLEN);
+            char *l_str_to_send = dap_strdup_printf("GET /%s/ping_sub_url HTTP/1.1\r\nHost: %s\r\n\r\n",
+            DAP_UPLINK_PATH_ENC_INIT, str_ip4);
+            // send data to bad suburl
+            int l_send_count = send(l_socket, l_str_to_send, dap_strlen(l_str_to_send), 0);
+            long l_recv_count = 0;
+            // recv data with error message
+            if(l_send_count > 30)
+                l_recv_count = s_recv(l_socket, l_buf, l_buf_size, 1000);
+            // connect/send/recv was successful
+            if(l_recv_count > 20) {
+                clock_gettime(CLOCK_MONOTONIC, &l_time_stop);
+                res = timespec_diff(&l_time_start, &l_time_stop, NULL);
+            }
+            DAP_DELETE(l_str_to_send);
+        }
+        else {
+            ; //log_it(L_INFO, "Can't connect to node for ping");
+        }
+        closesocket(l_socket);
+    }
+    return (void*) res;
+}
+
+// start sending ping
+int start_node_ping(pthread_t *a_thread, struct in_addr a_addr, int a_port, int a_count)
+{
+    uint8_t *l_data = DAP_NEW_Z_SIZE(uint8_t, sizeof(struct in_addr) + 2 * sizeof(int));
+    memcpy(l_data, &a_count, sizeof(int));
+    memcpy(l_data + sizeof(int), &a_port, sizeof(int));
+    memcpy(l_data + 2 * sizeof(int), &a_addr, sizeof(struct in_addr));
+    pthread_create(a_thread, NULL, node_ping_proc, l_data);
+    return 0;
+}
+
+// wait for ending ping within timeout_ms milliseconds
+int wait_node_ping(pthread_t l_thread, int timeout_ms)
+{
+    int l_ping_time = 0;
+    struct timespec l_wait_time;
+    clock_gettime(CLOCK_REALTIME, &l_wait_time);
+
+    timeout_ms *= 1000;
+    l_wait_time.tv_sec += timeout_ms / DAP_USEC_PER_SEC;
+    l_wait_time.tv_nsec += 1000 * (timeout_ms % DAP_USEC_PER_SEC);
+#if !defined(_WIN32) && !defined(__ANDROID__)
+    int res = pthread_timedjoin_np(l_thread, (void **) &l_ping_time, &l_wait_time);
+#else
+    int res = pthread_join(l_thread, (void **) &l_ping_time);
+#endif
+    if(res == ETIMEDOUT) {
+        pthread_kill(l_thread, 3); // SIGQUIT SIGABRT
+    }
+    else if(!res)
+        return l_ping_time;
+    return -1;
+}
+
+static dap_chain_node_addr_t *s_node_addr_tr = NULL, *s_node_addr_ping = NULL;
+
+static void* node_ping_background_proc(void *a_arg)
+{
+    dap_chain_net_t *l_net;
+    dap_list_t *l_node_list;
+    memcpy(&l_net, a_arg, sizeof(dap_chain_net_t*));
+    memcpy(&l_node_list, a_arg + sizeof(dap_chain_net_t*), sizeof(dap_list_t*));
+    DAP_DELETE(a_arg);
+    dap_chain_node_addr_t l_node_addr = { 0 };
+
+
+    // select the nearest node from the list
+    unsigned int l_nodes_count = dap_list_length(l_node_list);
+    unsigned int l_thread_id = 0;
+    pthread_t *l_threads = DAP_NEW_Z_SIZE(pthread_t, sizeof(pthread_t) * l_nodes_count);
+    uint64_t *l_nodes_addr = DAP_NEW_Z_SIZE(uint64_t, sizeof(uint64_t) * l_nodes_count);
+    dap_list_t *l_node_list0 = l_node_list;
+
+    dap_chain_node_addr_t *s_node_addr_tr = NULL, *l_node_addr_ping = NULL;
+    int l_min_hops = INT32_MAX;
+    int l_min_ping = INT32_MAX;
+    // send ping to all nodes
+    while(l_node_list) {
+        dap_chain_node_addr_t *l_node_addr = l_node_list->data;
+        dap_chain_node_info_t *l_node_info = dap_chain_node_info_read(l_net, l_node_addr);
+
+
+        char *host4 = DAP_NEW_SIZE(char, INET_ADDRSTRLEN);
+        struct sockaddr_in sa4 = { .sin_family = AF_INET, .sin_addr = l_node_info->hdr.ext_addr_v4 };
+        const char* str_ip4 = inet_ntop(AF_INET, &(((struct sockaddr_in *) &sa4)->sin_addr), host4, INET_ADDRSTRLEN);
+        if(!str_ip4)
+            continue;
+        int hops = 0, time_usec = 0;
+#ifndef _WIN32
+        int res = traceroute_util(str_ip4, &hops, &time_usec);
+#endif
+        DAP_DELETE(host4);
+        if(l_min_hops>hops)
+            s_node_addr_tr = l_node_list->data;
+
+        // start sending ping
+        start_node_ping(&l_threads[l_thread_id], l_node_info->hdr.ext_addr_v4, l_node_info->hdr.ext_port, 1);
+
+        l_nodes_addr[l_thread_id] = l_node_info->hdr.address.uint64;
+        l_thread_id++;
+        DAP_DELETE(l_node_info);
+        l_node_list = dap_list_next(l_node_list);
+    }
+    // wait for reply from nodes
+    int best_node_pos = -1;
+    int best_node_reply = INT32_MAX;
+    // timeout for all threads
+    int l_timeout_full_ms = 3000; // wait max 3 second
+    for(l_thread_id = 0; l_thread_id < l_nodes_count; l_thread_id++) {
+        if(l_timeout_full_ms < 100)
+            l_timeout_full_ms = 100; // make small timeout anyway, may be
+        struct timespec l_time_start;
+        clock_gettime(CLOCK_MONOTONIC, &l_time_start);
+        int res = wait_node_ping(l_threads[l_thread_id], l_timeout_full_ms);
+        if(res > 0 && res < best_node_reply) {
+            best_node_pos = l_thread_id;
+            s_node_addr_ping = l_node_list->data;
+            best_node_reply = res;
+        }
+        struct timespec l_time_stop;
+        clock_gettime(CLOCK_MONOTONIC, &l_time_stop);
+        l_timeout_full_ms -= timespec_diff(&l_time_start, &l_time_stop, NULL);
+        //printf(" thread %x ping=%d\n", l_threads[l_thread_id], res);
+    }
+    if(best_node_pos > 0) {
+        l_node_addr.uint64 = l_nodes_addr[best_node_pos];
+    }
+
+    // allocate memory for best node addresses
+    dap_chain_node_addr_t *s_node_addr_tmp;
+    s_node_addr_tmp = DAP_NEW(dap_chain_node_addr_t);
+    memcpy(s_node_addr_tmp, s_node_addr_tr, sizeof(dap_chain_node_addr_t));
+    s_node_addr_tr = s_node_addr_tmp;
+    s_node_addr_tmp = DAP_NEW(dap_chain_node_addr_t);
+    memcpy(s_node_addr_tmp, s_node_addr_ping, sizeof(dap_chain_node_addr_t));
+    s_node_addr_ping = s_node_addr_tmp;
+
+    // delete memory
+    DAP_DELETE(l_nodes_addr);
+    DAP_DELETE(l_threads);
+    dap_list_free_full(l_node_list0, free);
+    return 0;
+}
+
+static pthread_t s_thread = 0;
+
+// start background thread for testing connect to the nodes
+int dap_chain_node_ping_background_start(dap_chain_net_t *a_net, dap_list_t *a_node_list)
+{
+    if(!a_node_list)
+        return -1;
+    // already started
+    if(s_thread)
+        return 0;
+    // copy list
+    dap_list_t *l_node_list = NULL;
+    dap_list_t *l_node_list_tmp = a_node_list;
+    while(l_node_list_tmp) {
+        dap_chain_node_addr_t *l_addr = DAP_NEW(dap_chain_node_addr_t);
+        memcpy(l_addr, l_node_list_tmp->data, sizeof(dap_chain_node_addr_t));
+        l_node_list = dap_list_append(l_node_list, l_addr);
+        l_node_list_tmp = dap_list_next(l_node_list_tmp);
+    }
+    // start searching for better nodes
+    uint8_t *l_arg = DAP_NEW_SIZE(uint8_t, sizeof(dap_chain_net_t*) + sizeof(dap_chain_net_t*));
+    memcpy(l_arg, &a_net, sizeof(dap_chain_net_t*));
+    memcpy(l_arg + sizeof(dap_chain_net_t*), &l_node_list, sizeof(dap_list_t*));
+    pthread_create(&s_thread, NULL, node_ping_background_proc, l_arg);
+    return 0;
+}
+
+const dap_chain_node_addr_t* dap_chain_node_ping_get_node_tr(void)
+{
+    return s_node_addr_tr;
+}
+
+const dap_chain_node_addr_t* dap_chain_node_ping_get_node_ping(void)
+{
+    return s_node_addr_ping;
+}
+
+int dap_chain_node_ping_background_stop(void)
+{
+    int l_ret = wait_node_ping(s_thread, 500);
+    s_thread = 0;
+    return l_ret;
+}
+
+int dap_chain_node_ping_background_status(void)
+{
+    if(s_thread)
+        return 1;
+    return 0;
+}
diff --git a/libdap-chain-net/dap_chain_node_ping.h b/libdap-chain-net/dap_chain_node_ping.h
new file mode 100644
index 0000000000000000000000000000000000000000..a44f4fc25966047b079e9f6d15daee117daa6150
--- /dev/null
+++ b/libdap-chain-net/dap_chain_node_ping.h
@@ -0,0 +1,40 @@
+/*
+ * Authors:
+ * Alexander Lysikov <alexander.lysikov@demlabs.net>
+ * DeM Labs Inc.   https://demlabs.net
+
+ This file is part of DAP (Deus Applications Prototypes) the open source project
+
+ DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ DAP is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with any DAP based project.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+#ifndef _WIN32
+#include <netinet/in.h>
+#else
+#include <windows.h>
+#endif
+#include <pthread.h>
+
+// start sending ping
+int start_node_ping(pthread_t *a_thread, struct in_addr a_addr, int a_port, int a_count);
+
+// wait for ending ping within timeout_ms milliseconds
+int wait_node_ping(pthread_t l_thread, int timeout_ms);
+
+
+// background thread for testing connect to the nodes
+int dap_chain_node_ping_background_start(dap_chain_net_t *a_net, dap_list_t *a_node_list);
+int dap_chain_node_ping_background_stop(void);
+int dap_chain_node_ping_background_status(void);
diff --git a/libdap-chain-net/dap_chain_node_remote.c b/libdap-chain-net/dap_chain_node_remote.c
new file mode 100644
index 0000000000000000000000000000000000000000..cabf4dd34e4b5e07bcf67b93169b5a38f6a54636
--- /dev/null
+++ b/libdap-chain-net/dap_chain_node_remote.c
@@ -0,0 +1,149 @@
+/*
+ * Authors:
+ * Dmitriy A. Gearasimov <naeper@demlabs.net>
+ * DeM Labs Inc.   https://demlabs.net
+
+ This file is part of DAP (Deus Applications Prototypes) the open source project
+
+ DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ DAP is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with any DAP based project.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <pthread.h>
+
+#ifdef WIN32
+#include <winsock2.h>
+#include <windows.h>
+#include <mswsock.h>
+#include <ws2tcpip.h>
+#include <io.h>
+#endif
+
+#include "uthash.h"
+#include "dap_common.h"
+#include "dap_chain_node_remote.h"
+
+typedef struct dap_chain_node_link_item {
+    dap_chain_node_addr_t address;
+    dap_chain_node_client_t *client;
+    UT_hash_handle hh;
+} dap_chain_node_link_item_t;
+
+// List of connections
+static dap_chain_node_link_item_t *conn_list = NULL;
+
+// for separate access to connect_list
+static pthread_mutex_t connect_list_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+
+/**
+ * Add new established connection to the list
+ *
+ * return 0 OK, -1 error, -2 already present
+ */
+int dap_chain_node_client_list_add(dap_chain_node_addr_t *a_address, dap_chain_node_client_t *a_client)
+{
+    int l_ret = 0;
+    if(!a_address || !a_client)
+        return -1;
+    dap_chain_node_link_item_t *item_tmp = NULL;
+    pthread_mutex_lock(&connect_list_mutex);
+    HASH_FIND(hh, conn_list, a_address, sizeof(dap_chain_node_addr_t), item_tmp); // address already in the hash?
+    if(item_tmp == NULL) {
+        item_tmp = DAP_NEW(dap_chain_node_link_item_t);
+        item_tmp->address.uint64 = a_address->uint64;
+        item_tmp->client = a_client;
+        HASH_ADD(hh, conn_list, address, sizeof(dap_chain_node_addr_t), item_tmp); // address: name of key field
+        l_ret = 0;
+    }
+    // connection already present
+    else
+        l_ret = -2;
+    //connect_list = g_list_append(connect_list, client);
+    pthread_mutex_unlock(&connect_list_mutex);
+    return l_ret;
+}
+
+/**
+ * Delete established connection from the list
+ *
+ * return 0 OK, -1 error, -2 address not found
+ */
+int chain_node_client_list_del(dap_chain_node_addr_t *address)
+{
+    int ret = -1;
+    if(!address)
+        return -1;
+    dap_chain_node_link_item_t *item_tmp;
+    pthread_mutex_lock(&connect_list_mutex);
+    HASH_FIND(hh, conn_list, address, sizeof(dap_chain_node_addr_t), item_tmp);
+    if(item_tmp != NULL) {
+        HASH_DEL(conn_list, item_tmp);
+        ret = 0;
+    }
+    else
+        // address not found in the hash
+        ret = -2;
+    pthread_mutex_unlock(&connect_list_mutex);
+    if(!ret) {
+        // close connection
+        dap_chain_node_client_close(item_tmp->client);
+        // del struct for hash
+        DAP_DELETE(item_tmp);
+    }
+    return ret;
+}
+
+/**
+ * Delete all established connection from the list
+ */
+void chain_node_client_list_del_all(void)
+{
+    int ret = -1;
+    dap_chain_node_link_item_t *iter_current, *item_tmp;
+    pthread_mutex_lock(&connect_list_mutex);
+    HASH_ITER(hh, conn_list , iter_current, item_tmp) {
+        // close connection
+        dap_chain_node_client_close(iter_current->client);
+        // del struct for hash
+        HASH_DEL(conn_list, iter_current);
+    }
+    pthread_mutex_unlock(&connect_list_mutex);
+}
+
+/**
+ * Get present established connection by address
+ *
+ * return client, or NULL if the connection not found in the list
+ */
+const dap_chain_node_client_t* chain_node_client_find(dap_chain_node_addr_t *address)
+{
+    int ret = 0;
+    if(!address)
+        return NULL;
+    dap_chain_node_client_t *client_ret = NULL;
+    dap_chain_node_link_item_t *item_tmp;
+    pthread_mutex_lock(&connect_list_mutex);
+    HASH_FIND(hh, conn_list, address, sizeof(dap_chain_node_addr_t), item_tmp); // address already in the hash?
+    if(item_tmp != NULL) {
+        client_ret = item_tmp->client;
+    }
+    pthread_mutex_unlock(&connect_list_mutex);
+    return client_ret;
+}
diff --git a/libdap-chain-net/dap_chain_node_remote.h b/libdap-chain-net/dap_chain_node_remote.h
new file mode 100644
index 0000000000000000000000000000000000000000..ce8aedc93262ac6e2a2bf67cb27529d78726d50c
--- /dev/null
+++ b/libdap-chain-net/dap_chain_node_remote.h
@@ -0,0 +1,53 @@
+/*
+ * Authors:
+ * Dmitriy A. Gearasimov <naeper@demlabs.net>
+ * DeM Labs Inc.   https://demlabs.net
+
+ This file is part of DAP (Deus Applications Prototypes) the open source project
+
+ DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ DAP is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with any DAP based project.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <stdbool.h>
+
+#include "dap_chain_node.h"
+#include "dap_chain_node_client.h"
+
+/**
+ * Add new established connection to the list
+ *
+ * return 0 OK, -1 error, -2 already present
+ */
+int dap_chain_node_client_list_add(dap_chain_node_addr_t *address, dap_chain_node_client_t *client);
+
+/**
+ * Delete established connection from the list
+ *
+ * return 0 OK, -1 error, -2 address not found
+ */
+int chain_node_client_list_del(dap_chain_node_addr_t *address);
+
+/**
+ * Delete all established connection from the list
+ */
+void chain_node_client_list_del_all(void);
+
+/**
+ * Get present established connection by address
+ *
+ * return client, or NULL if the connection not found in the list
+ */
+const dap_chain_node_client_t* chain_node_client_find(dap_chain_node_addr_t *address);
diff --git a/libdap-chain-net/iputils/iputils.c b/libdap-chain-net/iputils/iputils.c
new file mode 100755
index 0000000000000000000000000000000000000000..263590139c76977ee7e4f7ce37800825fd27e542
--- /dev/null
+++ b/libdap-chain-net/iputils/iputils.c
@@ -0,0 +1,49 @@
+/*
+ * Set utilities for networking
+ */
+
+#include <stdio.h>
+#include <stdbool.h>
+#include "dap_common.h"
+#include "dap_strfuncs.h"
+
+static bool LOG_VERBOSE = false;
+
+/**
+ * Set verbose mode
+ */
+void iputils_set_verbose(void)
+{
+    LOG_VERBOSE = true;
+}
+
+/**
+ * Reset verbose mode
+ */
+void iputils_reset_verbose(void)
+{
+    LOG_VERBOSE = false;
+}
+
+// analog printf()
+int log_printf(const char *format, ...)
+{
+    int ret = 0;
+    if(LOG_VERBOSE)
+    {
+        char *log_str = NULL;
+        va_list args;
+
+        va_start(args, format);
+        log_str = dap_strdup_vprintf(format, args);
+        va_end(args);
+
+        if(log_str)
+        {
+
+            ret = printf("%s", log_str);
+            DAP_DELETE(log_str);
+        }
+    }
+    return ret;
+}
diff --git a/libdap-chain-net/iputils/iputils.h b/libdap-chain-net/iputils/iputils.h
new file mode 100644
index 0000000000000000000000000000000000000000..0330b548f1a23b5b677a4218683a933a169669e5
--- /dev/null
+++ b/libdap-chain-net/iputils/iputils.h
@@ -0,0 +1,189 @@
+/*
+ * Set utilities for networking
+ */
+
+#ifndef _IPUTILS_H
+#define _IPUTILS_H
+
+#include <stdint.h>
+#include <stdlib.h>
+#ifndef _WIN32
+#include <netinet/ip.h>
+#else
+#include <winsock2.h>
+#include <windows.h>
+#include <mswsock.h>
+#include <ws2tcpip.h>
+#include <io.h>
+#include "win32/iphdr.h"
+#include "win32/ip.h"
+#define uid_t uint32_t
+#endif
+#include <setjmp.h>
+#include <sys/time.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define MAXPACKET 128000    /* max packet size */
+
+#define MAX_DUP_CHK 0x10000
+
+#ifdef USE_BITMAP64
+typedef uint64_t  bitmap_t;
+# define BITMAP_SHIFT 6
+#else
+typedef uint32_t  bitmap_t;
+# define BITMAP_SHIFT 5
+#endif
+
+struct rcvd_table {
+  bitmap_t bitmap[MAX_DUP_CHK / (sizeof(bitmap_t) * 8)];
+};
+
+typedef struct ping_handle{
+    int ts_type;
+    int nroute;
+    uint32_t route[10];
+
+    struct sockaddr_in whereto; /* who to ping */
+    int optlen;
+    int settos; /* Set TOS, Precendence or other QOS options */
+
+    int broadcast_pings;
+
+    struct sockaddr_in source;
+    char *device;
+    int pmtudisc;
+    struct {
+
+        int options;
+
+        int mark;
+        int sndbuf;
+        int ttl;
+        int rtt;
+        int rtt_addend;
+        uint16_t acked;
+
+        unsigned char outpack[MAXPACKET];
+        struct rcvd_table rcvd_tbl;
+
+        // counters
+        long npackets; // max packets to transmit
+        long nreceived; // # of packets we got back
+        long nrepeats; // number of duplicates
+        long ntransmitted; // sequence # for outbound packets = #sent
+        long nchecksum; // replies with bad checksum
+        long nerrors; // icmp errors
+        int interval; // interval between packets (msec)
+        int preload;
+        int deadline; // time to die
+        int lingertime;
+        struct timeval start_time, cur_time;
+        //volatile int exiting;
+        //volatile int status_snapshot;
+        int confirm;
+        volatile int in_pr_addr; // pr_addr() is executing
+        jmp_buf pr_addr_jmp;
+
+        /* Stupid workarounds for bugs/missing functionality in older linuces.
+         * confirm_flag fixes refusing service of kernels without MSG_CONFIRM.
+         * i.e. for linux-2.2 */
+        int confirm_flag;
+
+        // timing
+        int timing; // flag to do timing
+        long tmin; // minimum round trip time
+        long tmax; // maximum round trip time
+        /* Message for rpm maintainers: have _shame_. If you want
+         * to fix something send the patch to me for sanity checking.
+         * "sparcfix" patch is a complete non-sense, apparenly the person
+         * prepared it was stoned.
+         */
+        long long tsum; // sum of all times, for doing average
+        long long tsum2;
+        int pipesize;
+
+        int datalen;
+
+        char *hostname;
+        int uid;
+        uid_t euid;
+        int ident; // process id to identify our packets
+
+        int screen_width;
+
+        #ifdef HAVE_LIBCAP
+        cap_value_t cap_raw;
+        cap_value_t cap_admin;
+        #endif
+    } ping_common;
+}ping_handle_t;
+
+ping_handle_t* ping_handle_create(void);
+
+/**
+ * Send ping for ipv4
+ *
+ * @addr host name or IP address
+ * @count number of packets to transmit
+ * @return ping time in microsecond or -1 if error
+ */
+int ping_util(ping_handle_t *a_ping_handle, const char *addr, int count);
+
+/**
+ * Send ping for ipv6
+ *
+ * @addr host name or IP address
+ * @count number of packets to transmit
+ * @return ping time in microsecond or -1 if error
+ */
+int ping_util6(ping_handle_t *a_ping_handle, const char *addr, int count);
+
+
+/**
+ * Tracepath host
+ *
+ * @addr[in] host name or IP address
+ * @hops[out] hops count
+ * @time_usec[out] latency in microseconds
+ * @return 0 Ok, -1 error
+ */
+int tracepath_util(const char *addr, int *hops, int *time_usec);
+
+/**
+ * Traceroute host
+ *
+ * @addr[in] host name or IP address
+ * @hops[out] hops count
+ * @time_usec[out] latency in microseconds
+ * @return 0 Ok, -1 error
+ */
+int traceroute_util(const char *addr, int *hops, int *time_usec);
+
+
+/**
+ * Set verbose mode
+ */
+void iputils_set_verbose(void);
+/**
+ * Reset verbose mode
+ */
+void iputils_reset_verbose(void);
+
+
+// analog printf()
+int log_printf(const char *format, ...);
+
+#define PACKAGE_NAME "iputils"
+#define PACKAGE_VERSION "0.1"
+#define IPUTILS_VERSION(_prog) "%s from %s %s\n", _prog, PACKAGE_NAME, PACKAGE_VERSION
+#define UNUSED(x) (void)(x)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _IPUTILS_H
diff --git a/libdap-chain-net/iputils/ping.c b/libdap-chain-net/iputils/ping.c
new file mode 100644
index 0000000000000000000000000000000000000000..e634a606e8bde2c67697719a6d1295cbc4960a1d
--- /dev/null
+++ b/libdap-chain-net/iputils/ping.c
@@ -0,0 +1,1694 @@
+/*
+ * Copyright (c) 1989 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Mike Muuss.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+/*
+ *			P I N G . C
+ *
+ * Using the InterNet Control Message Protocol (ICMP) "ECHO" facility,
+ * measure round-trip-delays and packet loss across network paths.
+ *
+ * Author -
+ *	Mike Muuss
+ *	U. S. Army Ballistic Research Laboratory
+ *	December, 1983
+ *
+ * Status -
+ *	Public Domain.  Distribution Unlimited.
+ * Bugs -
+ *	More statistics could always be gathered.
+ *	If kernel does not support non-raw ICMP sockets,
+ *	this program has to run SUID to ROOT or with
+ *	net_cap_raw enabled.
+ */
+
+#include "ping.h"
+
+#include <assert.h>
+#include <netinet/ip_icmp.h>
+#include <ifaddrs.h>
+#include <math.h>
+#include "dap_common.h"
+#include "dap_strfuncs.h"
+#include "iputils.h"
+
+#ifndef ICMP_FILTER
+#define ICMP_FILTER	1
+struct icmp_filter {
+    uint32_t data;
+};
+#endif
+
+ping_func_set_st ping4_func_set = {
+    .send_probe = ping4_send_probe,
+    .receive_error_msg = ping4_receive_error_msg,
+    .parse_reply = ping4_parse_reply,
+    .install_filter = ping4_install_filter
+};
+
+#define	MAXIPLEN	60
+#define	MAXICMPLEN	76
+#define	NROUTES		9		/* number of record route slots */
+#define TOS_MAX		255		/* 8-bit TOS field */
+
+/*
+static int ts_type;
+static int nroute = 0;
+static uint32_t route[10];
+
+static struct sockaddr_in whereto; // who to ping
+static int optlen = 0;
+static int settos = 0; // Set TOS, Precendence or other QOS options
+
+static int broadcast_pings = 0;
+
+static struct sockaddr_in source = { .sin_family = AF_INET };
+char *device;
+int pmtudisc = -1;
+*/
+
+static void pr_options(ping_handle_t *a_ping_handle, unsigned char * cp, int hlen);
+static void pr_iph(ping_handle_t *a_ping_handle, struct iphdr *ip);
+static unsigned short in_cksum(const unsigned short *addr, int len, unsigned short salt);
+static void pr_icmph(ping_handle_t *a_ping_handle, uint8_t type, uint8_t code, uint32_t info, struct icmphdr *icp);
+static int parsetos(char *str);
+static int parseflow(char *str);
+
+ping_handle_t* ping_handle_create(void)
+{
+    ping_handle_t *lping = DAP_NEW_Z(ping_handle_t);
+    lping->source.sin_family = AF_INET;
+    lping->pmtudisc = -1;
+
+    lping->ping_common.interval = 1000; // interval between packets (msec)
+    lping->ping_common.preload = 1;
+    lping->ping_common.lingertime = MAXWAIT * 1000;
+
+
+    lping->ping_common.confirm_flag = MSG_CONFIRM;
+
+    lping->ping_common.tmin = LONG_MAX; // minimum round trip time
+    lping->ping_common.pipesize = -1;
+
+    lping->ping_common.datalen = DEFDATALEN;
+
+    lping->ping_common.screen_width = INT_MAX;
+
+#ifdef HAVE_LIBCAP
+    lping->ping_common.cap_raw = CAP_NET_RAW;
+    lping->ping_common.cap_admin = CAP_NET_ADMIN;
+#endif
+
+    return lping;
+}
+
+static void create_socket(ping_handle_t *a_ping_handle, socket_st *sock, int family, int socktype, int protocol, int requisite)
+{
+    int do_fallback = 0;
+
+    errno = 0;
+
+    assert(sock->fd == -1);
+    assert(socktype == SOCK_DGRAM || socktype == SOCK_RAW);
+
+    /* Attempt to create a ping socket if requested. Attempt to create a raw
+     * socket otherwise or as a fallback. Well known errno values follow.
+     *
+     * 1) EACCES
+     *
+     * Kernel returns EACCES for all ping socket creation attempts when the
+     * user isn't allowed to use ping socket. A range of group ids is
+     * configured using the `net.ipv4.ping_group_range` sysctl. Fallback
+     * to raw socket is necessary.
+     *
+     * Kernel returns EACCES for all raw socket creation attempts when the
+     * process doesn't have the `CAP_NET_RAW` capability.
+     *
+     * 2) EAFNOSUPPORT
+     *
+     * Kernel returns EAFNOSUPPORT for IPv6 ping or raw socket creation
+     * attempts when run with IPv6 support disabled (e.g. via `ipv6.disable=1`
+     * kernel command-line option.
+     *
+     * https://github.com/iputils/iputils/issues/32
+     *
+     * OpenVZ 2.6.32-042stab113.11 and possibly other older kernels return
+     * EAFNOSUPPORT for all IPv4 ping socket creation attempts due to lack
+     * of support in the kernel. Fallback to raw socket is necessary.
+     *
+     * https://github.com/iputils/iputils/issues/54
+     *
+     * 3) EPROTONOSUPPORT
+     *
+     * OpenVZ 2.6.32-042stab113.11 and possibly other older kernels return
+     * EPROTONOSUPPORT for all IPv6 ping socket creation attempts due to lack
+     * of support in the kernel [1]. Debian 9.5 based container with kernel 4.10
+     * returns EPROTONOSUPPORT also for IPv4 [2]. Fallback to raw socket is
+     * necessary.
+     *
+     * [1] https://github.com/iputils/iputils/issues/54
+     * [2] https://github.com/iputils/iputils/issues/129
+     */
+    if(socktype == SOCK_DGRAM)
+        sock->fd = socket(family, socktype, protocol);
+
+    /* Kernel doesn't support ping sockets. */
+    if(sock->fd == -1 && errno == EAFNOSUPPORT && family == AF_INET)
+        do_fallback = 1;
+    if(sock->fd == -1 && errno == EPROTONOSUPPORT)
+        do_fallback = 1;
+
+    /* User is not allowed to use ping sockets. */
+    if(sock->fd == -1 && errno == EACCES)
+        do_fallback = 1;
+
+    if(socktype == SOCK_RAW || do_fallback) {
+        socktype = SOCK_RAW;
+        sock->fd = socket(family, SOCK_RAW, protocol);
+    }
+
+    if(sock->fd == -1) {
+        /* Report error related to disabled IPv6 only when IPv6 also failed or in
+         * verbose mode. Report other errors always.
+         */
+        if((errno == EAFNOSUPPORT && socktype == AF_INET6) || (a_ping_handle->ping_common.options & F_VERBOSE) || requisite)
+            error(0, errno, "socket");
+        if(requisite)
+            exit(2);
+    } else
+        sock->socktype = socktype;
+}
+
+static void set_socket_option(socket_st *sock, int level, int optname, const void *optval, socklen_t olen)
+{
+    if(sock->fd == -1)
+        return;
+
+    if(setsockopt(sock->fd, level, optname, optval, olen) == -1)
+        error(2, errno, "setsockopt");
+}
+
+/* Much like stdtod(3, but will fails if str is not valid number. */
+static double ping_strtod(const char *str, const char *err_msg)
+{
+    double num;
+    char *end = NULL;
+
+    if(str == NULL || *str == '\0')
+        goto err;
+    errno = 0;
+#ifdef USE_IDN
+    setlocale(LC_ALL, "C");
+#endif
+    num = strtod(str, &end);
+#ifdef USE_IDN
+    setlocale(LC_ALL, "");
+#endif
+    if(errno || str == end || (end && *end))
+        goto err;
+    switch (fpclassify(num)) {
+    case FP_NORMAL:
+        case FP_ZERO:
+        break;
+    default:
+        errno = ERANGE;
+        goto err;
+    }
+    return num;
+    err:
+    error(2, errno, "%s: %s", err_msg, str);
+    abort(); /* cannot be reached, above error() will exit */
+    return 0.0;
+}
+
+static int ping_main(ping_handle_t *a_ping_handle, int argc, char **argv)
+{
+    struct addrinfo hints = { .ai_family = AF_UNSPEC, .ai_protocol = IPPROTO_UDP, .ai_socktype = SOCK_DGRAM, .ai_flags =
+    getaddrinfo_flags };
+    struct addrinfo *result, *ai;
+    int status;
+    int ch;
+    socket_st sock4 = { .fd = -1 };
+    socket_st sock6 = { .fd = -1 };
+    char *target;
+
+    limit_capabilities(a_ping_handle);
+
+#ifdef USE_IDN
+    setlocale(LC_ALL, "");
+    if (!strcmp(setlocale(LC_ALL, NULL), "C"))
+    hints.ai_flags &= ~ AI_CANONIDN;
+#endif
+
+    /* Support being called using `ping4` or `ping6` symlinks */
+    if(argv[0][strlen(argv[0]) - 1] == '4')
+        hints.ai_family = AF_INET;
+    else if(argv[0][strlen(argv[0]) - 1] == '6')
+        hints.ai_family = AF_INET6;
+
+    /* Parse command line options */
+    while((ch = getopt(argc, argv, "h?" "4bRT:" "6F:N:" "aABc:dDfi:I:l:Lm:M:nOp:qQ:rs:S:t:UvVw:W:")) != EOF) {
+        switch (ch) {
+        /* IPv4 specific options */
+        case '4':
+            if(hints.ai_family != AF_UNSPEC)
+                error(2, 0, "only one -4 or -6 option may be specified");
+            hints.ai_family = AF_INET;
+            break;
+        case 'b':
+            a_ping_handle->broadcast_pings = 1;
+            break;
+        case 'R':
+            if(a_ping_handle->ping_common.options & F_TIMESTAMP)
+                error(2, 0, "only one of -T or -R may be used");
+            a_ping_handle->ping_common.options |= F_RROUTE;
+            break;
+        case 'T':
+            if(a_ping_handle->ping_common.options & F_RROUTE)
+                error(2, 0, "only one of -T or -R may be used");
+            a_ping_handle->ping_common.options |= F_TIMESTAMP;
+            if(strcmp(optarg, "tsonly") == 0)
+                a_ping_handle->ts_type = IPOPT_TS_TSONLY;
+            else if(strcmp(optarg, "tsandaddr") == 0)
+                a_ping_handle->ts_type = IPOPT_TS_TSANDADDR;
+            else if(strcmp(optarg, "tsprespec") == 0)
+                a_ping_handle->ts_type = IPOPT_TS_PRESPEC;
+            else
+                error(2, 0, "invalid timestamp type: %s", optarg);
+            break;
+            /* IPv6 specific options */
+        case '6':
+            if(hints.ai_family != AF_UNSPEC)
+                error(2, 0, "only one -4 or -6 option may be specified");
+            hints.ai_family = AF_INET6;
+            break;
+        case 'F':
+            flowlabel = parseflow(optarg);
+            a_ping_handle->ping_common.options |= F_FLOWINFO;
+            break;
+        case 'N':
+            if(niquery_option_handler(optarg) < 0)
+                usage();
+            hints.ai_socktype = SOCK_RAW;
+            break;
+            /* Common options */
+        case 'a':
+            a_ping_handle->ping_common.options |= F_AUDIBLE;
+            break;
+        case 'A':
+            a_ping_handle->ping_common.options |= F_ADAPTIVE;
+            break;
+        case 'B':
+            a_ping_handle->ping_common.options |= F_STRICTSOURCE;
+            break;
+        case 'c':
+            a_ping_handle->ping_common.npackets = atoi(optarg);
+            if(a_ping_handle->ping_common.npackets <= 0)
+                error(2, 0, "bad number of packets to transmit: %ld", a_ping_handle->ping_common.npackets);
+            break;
+        case 'd':
+            a_ping_handle->ping_common.options |= F_SO_DEBUG;
+            break;
+        case 'D':
+            a_ping_handle->ping_common.options |= F_PTIMEOFDAY;
+            break;
+        case 'i':
+            {
+            double optval;
+
+            optval = ping_strtod(optarg, "bad timing interval");
+            if(isgreater(optval, (double)(INT_MAX / 1000)))
+                error(2, 0, "bad timing interval: %s", optarg);
+            a_ping_handle->ping_common.interval = (int) (optval * 1000);
+            a_ping_handle->ping_common.options |= F_INTERVAL;
+        }
+            break;
+        case 'I':
+            /* IPv6 */
+            if(strchr(optarg, ':')) {
+                char *p, *addr = strdup(optarg);
+
+                if(!addr)
+                    error(2, errno, "cannot copy: %s", optarg);
+
+                p = strchr(addr, SCOPE_DELIMITER);
+                if(p) {
+                    *p = '\0';
+                    a_ping_handle->device = optarg + (p - addr) + 1;
+                }
+
+                if(inet_pton(AF_INET6, addr, (char*) &source6.sin6_addr) <= 0)
+                    error(2, 0, "invalid source address: %s", optarg);
+
+                a_ping_handle->ping_common.options |= F_STRICTSOURCE;
+
+                free(addr);
+            } else if(inet_pton(AF_INET, optarg, &a_ping_handle->source.sin_addr) > 0) {
+                a_ping_handle->ping_common.options |= F_STRICTSOURCE;
+            } else {
+                a_ping_handle->device = optarg;
+            }
+            break;
+        case 'l':
+            a_ping_handle->ping_common.preload = atoi(optarg);
+            if(a_ping_handle->ping_common.preload <= 0)
+                error(2, 0, "bad preload value: %s, should be 1..%d", optarg, MAX_DUP_CHK);
+            if(a_ping_handle->ping_common.preload > MAX_DUP_CHK)
+                a_ping_handle->ping_common.preload = MAX_DUP_CHK;
+            if(a_ping_handle->ping_common.uid && a_ping_handle->ping_common.preload > 3)
+                error(2, 0, "cannot set preload to value greater than 3: %d", a_ping_handle->ping_common.preload);
+            break;
+        case 'L':
+            a_ping_handle->ping_common.options |= F_NOLOOP;
+            break;
+        case 'm':
+            {
+            char *endp;
+            a_ping_handle->ping_common.mark = (int) strtoul(optarg, &endp, 10);
+            if(a_ping_handle->ping_common.mark < 0 || *endp != '\0')
+                error(2, 0, "mark cannot be negative: %s", optarg);
+            a_ping_handle->ping_common.options |= F_MARK;
+            break;
+        }
+        case 'M':
+            if(strcmp(optarg, "do") == 0)
+                a_ping_handle->pmtudisc = IP_PMTUDISC_DO;
+            else if(strcmp(optarg, "dont") == 0)
+                a_ping_handle->pmtudisc = IP_PMTUDISC_DONT;
+            else if(strcmp(optarg, "want") == 0)
+                a_ping_handle->pmtudisc = IP_PMTUDISC_WANT;
+            else
+                error(2, 0, "invalid -M argument: %s", optarg);
+            break;
+        case 'n':
+            a_ping_handle->ping_common.options |= F_NUMERIC;
+            break;
+        case 'O':
+            a_ping_handle->ping_common.options |= F_OUTSTANDING;
+            break;
+        case 'f':
+            /* avoid `getaddrinfo()` during flood */
+            a_ping_handle->ping_common.options |= F_FLOOD | F_NUMERIC;
+            setbuf(stdout, (char *) NULL);
+            break;
+        case 'p':
+            a_ping_handle->ping_common.options |= F_PINGFILLED;
+            fill(a_ping_handle, optarg, a_ping_handle->ping_common.outpack, sizeof(a_ping_handle->ping_common.outpack));
+            break;
+        case 'q':
+            a_ping_handle->ping_common.options |= F_QUIET;
+            break;
+        case 'Q':
+            a_ping_handle->settos = parsetos(optarg); /* IPv4 */
+            tclass = a_ping_handle->settos; /* IPv6 */
+            break;
+        case 'r':
+            a_ping_handle->ping_common.options |= F_SO_DONTROUTE;
+            break;
+        case 's':
+            a_ping_handle->ping_common.datalen = atoi(optarg);
+            if(a_ping_handle->ping_common.datalen < 0)
+                error(2, 0, "illegal packet size: %d", a_ping_handle->ping_common.datalen);
+            if(a_ping_handle->ping_common.datalen > MAXPACKET - 8)
+                error(2, 0, "packet size too large: %d", a_ping_handle->ping_common.datalen);
+            break;
+        case 'S':
+            a_ping_handle->ping_common.sndbuf = atoi(optarg);
+            if(a_ping_handle->ping_common.sndbuf <= 0)
+                error(2, 0, "bad sndbuf value: %s", optarg);
+            break;
+        case 't':
+            a_ping_handle->ping_common.options |= F_TTL;
+            a_ping_handle->ping_common.ttl = atoi(optarg);
+            if(a_ping_handle->ping_common.ttl < 0 || a_ping_handle->ping_common.ttl > 255)
+                error(2, 0, "ttl out of range: %s", optarg);
+            break;
+        case 'U':
+            a_ping_handle->ping_common.options |= F_LATENCY;
+            break;
+        case 'v':
+            a_ping_handle->ping_common.options |= F_VERBOSE;
+            break;
+        case 'V':
+            printf(IPUTILS_VERSION("ping"));
+            exit(0);
+        case 'w':
+            a_ping_handle->ping_common.deadline = atoi(optarg);
+            if(a_ping_handle->ping_common.deadline < 0)
+                error(2, 0, "bad wait time: %s", optarg);
+            break;
+        case 'W':
+            {
+            double optval;
+
+            optval = ping_strtod(optarg, "bad linger time");
+            if(isless(optval, 0.001) || isgreater(optval, (double)(INT_MAX / 1000)))
+                error(2, 0, "bad linger time: %s", optarg);
+            /* lingertime will be converted to usec later */
+            a_ping_handle->ping_common.lingertime = (int) (optval * 1000);
+        }
+            break;
+        default:
+            usage();
+            break;
+        }
+    }
+
+    argc -= optind;
+    argv += optind;
+    optind = 0;
+
+    if(!argc)
+    {
+        //error(1, EDESTADDRREQ, "usage error");
+        return -EDESTADDRREQ;//    89  Destination address required
+    }
+
+    target = argv[argc - 1];
+
+    /* Create sockets */
+    enable_capability_raw(a_ping_handle);
+    if(hints.ai_family != AF_INET6)
+        create_socket(a_ping_handle, &sock4, AF_INET, hints.ai_socktype, IPPROTO_ICMP, hints.ai_family == AF_INET);
+    if(hints.ai_family != AF_INET) {
+        create_socket(a_ping_handle, &sock6, AF_INET6, hints.ai_socktype, IPPROTO_ICMPV6, sock4.fd == -1);
+        /* This may not be needed if both protocol versions always had the same value, but
+         * since I don't know that, it's better to be safe than sorry. */
+        a_ping_handle->pmtudisc = a_ping_handle->pmtudisc == IP_PMTUDISC_DO ? IPV6_PMTUDISC_DO :
+                                  a_ping_handle->pmtudisc == IP_PMTUDISC_DONT ? IPV6_PMTUDISC_DONT :
+                                  a_ping_handle->pmtudisc == IP_PMTUDISC_WANT ? IPV6_PMTUDISC_WANT : a_ping_handle->pmtudisc;
+    }
+    disable_capability_raw(a_ping_handle);
+
+    /* Limit address family on single-protocol systems */
+    if(hints.ai_family == AF_UNSPEC) {
+        if(sock4.fd == -1)
+            hints.ai_family = AF_INET6;
+        else if(sock6.fd == -1)
+            hints.ai_family = AF_INET;
+    }
+
+    /* Set socket options */
+    if(a_ping_handle->settos)
+        set_socket_option(&sock4, IPPROTO_IP, IP_TOS, &a_ping_handle->settos, sizeof (a_ping_handle->settos));
+    if(tclass)
+        set_socket_option(&sock6, IPPROTO_IPV6, IPV6_TCLASS, &tclass, sizeof tclass);
+
+    status = getaddrinfo(target, NULL, &hints, &result);
+    if(status)
+    {
+        //error(2, 0, "%s: %s", target, gai_strerror(status));
+        return -EADDRNOTAVAIL;//
+    }
+
+    for(ai = result; ai; ai = ai->ai_next) {
+        switch (ai->ai_family) {
+        case AF_INET:
+            status = ping4_run(a_ping_handle, argc, argv, ai, &sock4);
+            break;
+        case AF_INET6:
+            status = ping6_run(a_ping_handle, argc, argv, ai, &sock6);
+            break;
+        default:
+        {
+            //error(2, 0, "unknown protocol family: %d", ai->ai_family);
+            return -EPFNOSUPPORT;
+        }
+        }
+
+        if(status == 0)
+            break;
+    }
+
+    freeaddrinfo(result);
+
+    return status;
+}
+
+/**
+ * Send ping
+ *
+ * @type for ipv4=4, for ipv6=6
+ * @addr host name or IP address
+ * @count number of packets to transmit
+ * @return ping time in microsecond or -1 if error
+ */
+int ping_util_common(ping_handle_t *a_ping_handle, int type, const char *addr, int count)
+{
+
+    /*
+     rights for /bin/ping: -rwsr-xr-x 1 root root
+     current parametr:
+     # sysctl net.ipv4.ping_group_range
+     net.ipv4.ping_group_range = 1   0
+     Need change range for other users:
+     # sysctl net.ipv4.ping_group_range="1 65000"
+     */
+    a_ping_handle->ping_common.tsum = a_ping_handle->ping_common.ntransmitted = a_ping_handle->ping_common.nreceived = exiting = 0;
+    int argc = 3;
+    const char *argv[argc];
+    if(type != 4)
+        argv[0] = "ping6";
+    else
+        argv[0] = "ping4";
+    argv[1] = dap_strdup_printf("-c%d", count);
+    argv[2] = addr;
+    int status = ping_main(a_ping_handle, argc, (char**) argv);
+    DAP_DELETE((char*) argv[1]);
+    if(a_ping_handle->ping_common.ntransmitted >= 1 && a_ping_handle->ping_common.nreceived >= 1)
+        return a_ping_handle->ping_common.tsum;
+    return status;
+}
+
+/**
+ * Send ping for ipv4
+ *
+ * @addr host name or IP address
+ * @count number of packets to transmit
+ * @return ping time in microsecond or -1 if error
+ */
+int ping_util(ping_handle_t *a_ping_handle, const char *addr, int count)
+{
+    return ping_util_common(a_ping_handle, 4, addr, count);
+}
+
+/**
+ * Send ping for ipv6
+ *
+ * @addr host name or IP address
+ * @count number of packets to transmit
+ * @return ping time in microsecond or -1 if error
+ */
+int ping_util6(ping_handle_t *a_ping_handle, const char *addr, int count)
+{
+    return ping_util_common(a_ping_handle, 6, addr, count);
+}
+
+int ping4_run(ping_handle_t *a_ping_handle, int argc, char **argv, struct addrinfo *ai, socket_st *sock)
+{
+    static const struct addrinfo hints = { .ai_family = AF_INET, .ai_protocol = IPPROTO_UDP, .ai_flags =
+    getaddrinfo_flags };
+    int hold, packlen;
+    unsigned char *packet;
+    char *target;
+    char hnamebuf[NI_MAXHOST];
+    unsigned char rspace[3 + 4 * NROUTES + 1]; /* record route space */
+    uint32_t *tmp_rspace;
+
+    if(argc > 1) {
+        if(a_ping_handle->ping_common.options & F_RROUTE)
+            usage();
+        else if(a_ping_handle->ping_common.options & F_TIMESTAMP) {
+            if(a_ping_handle->ts_type != IPOPT_TS_PRESPEC)
+                usage();
+            if(argc > 5)
+                usage();
+        } else {
+            if(argc > 10)
+                usage();
+            a_ping_handle->ping_common.options |= F_SOURCEROUTE;
+        }
+    }
+    while(argc > 0) {
+        target = *argv;
+
+        memset((char *) &a_ping_handle->whereto, 0, sizeof(a_ping_handle->whereto));
+        a_ping_handle->whereto.sin_family = AF_INET;
+        if(inet_aton(target, &a_ping_handle->whereto.sin_addr) == 1) {
+            a_ping_handle->ping_common.hostname = target;
+            if(argc == 1)
+                a_ping_handle->ping_common.options |= F_NUMERIC;
+        } else {
+            struct addrinfo *result = NULL;
+            int status;
+
+            if(argc > 1 || !ai) {
+                status = getaddrinfo(target, NULL, &hints, &result);
+                if(status)
+                    error(2, 0, "%s: %s", target, gai_strerror(status));
+                ai = result;
+            }
+
+            memcpy(&a_ping_handle->whereto, ai->ai_addr, sizeof (a_ping_handle->whereto));
+            memset(hnamebuf, 0, sizeof hnamebuf);
+            if(ai->ai_canonname)
+                strncpy(hnamebuf, ai->ai_canonname, sizeof hnamebuf - 1);
+            a_ping_handle->ping_common.hostname = hnamebuf;
+
+            if(result)
+                freeaddrinfo(result);
+        }
+        if(argc > 1)
+            a_ping_handle->route[a_ping_handle->nroute++] = a_ping_handle->whereto.sin_addr.s_addr;
+        argc--;
+        argv++;
+    }
+
+    if(a_ping_handle->source.sin_addr.s_addr == 0) {
+        socklen_t alen;
+        struct sockaddr_in dst = a_ping_handle->whereto;
+        int probe_fd = socket(AF_INET, SOCK_DGRAM, 0);
+
+        if(probe_fd < 0)
+            error(2, errno, "socket");
+        if(a_ping_handle->device) {
+            struct ifreq ifr;
+            int i;
+            int fds[2] = { probe_fd, sock->fd };
+
+            memset(&ifr, 0, sizeof(ifr));
+            strncpy(ifr.ifr_name, a_ping_handle->device, IFNAMSIZ - 1);
+
+            for(i = 0; i < 2; i++) {
+                int fd = fds[i];
+                int rc;
+                int errno_save;
+
+                enable_capability_raw(a_ping_handle);
+                rc = setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, a_ping_handle->device, strlen(a_ping_handle->device) + 1);
+                errno_save = errno;
+                disable_capability_raw(a_ping_handle);
+
+                if(rc == -1) {
+                    if(IN_MULTICAST(ntohl(dst.sin_addr.s_addr))) {
+                        struct ip_mreqn imr;
+                        if(ioctl(fd, SIOCGIFINDEX, &ifr) < 0)
+                            error(2, 0, "unknown iface: %s", a_ping_handle->device);
+                        memset(&imr, 0, sizeof(imr));
+                        imr.imr_ifindex = ifr.ifr_ifindex;
+                        if(setsockopt(fd, SOL_IP, IP_MULTICAST_IF, &imr, sizeof(imr)) == -1)
+                            error(2, errno, "IP_MULTICAST_IF");
+                    } else
+                        error(2, errno_save, "SO_BINDTODEVICE %s", a_ping_handle->device);
+                }
+            }
+        }
+
+        if(a_ping_handle->settos &&
+                setsockopt(probe_fd, IPPROTO_IP, IP_TOS, (char *) &a_ping_handle->settos, sizeof(int)) < 0)
+            error(0, errno, "warning: QOS sockopts");
+
+        dst.sin_port = htons(1025);
+        if(a_ping_handle->nroute)
+            dst.sin_addr.s_addr = a_ping_handle->route[0];
+        if(connect(probe_fd, (struct sockaddr*) &dst, sizeof(dst)) == -1) {
+            if(errno == EACCES) {
+                if(a_ping_handle->broadcast_pings == 0)
+                    error(2, 0,
+                            "Do you want to ping broadcast? Then -b. If not, check your local firewall rules");
+                fprintf(stderr, "WARNING: pinging broadcast address\n");
+                if(setsockopt(probe_fd, SOL_SOCKET, SO_BROADCAST,
+                        &a_ping_handle->broadcast_pings, sizeof(a_ping_handle->broadcast_pings)) < 0)
+                    error(2, errno, "cannot set broadcasting");
+                if(connect(probe_fd, (struct sockaddr*) &dst, sizeof(dst)) == -1)
+                    error(2, errno, "connect");
+            } else
+                error(2, errno, "connect");
+        }
+        alen = sizeof(a_ping_handle->source);
+        if(getsockname(probe_fd, (struct sockaddr*) &a_ping_handle->source, &alen) == -1)
+            error(2, errno, "getsockname");
+        a_ping_handle->source.sin_port = 0;
+
+        if(a_ping_handle->device) {
+            struct ifaddrs *ifa0, *ifa;
+            int ret;
+
+            ret = getifaddrs(&ifa0);
+            if(ret)
+                error(2, errno, "gatifaddrs failed");
+            for(ifa = ifa0; ifa; ifa = ifa->ifa_next) {
+                if(!ifa->ifa_name || !ifa->ifa_addr ||
+                        ifa->ifa_addr->sa_family != AF_INET)
+                    continue;
+                if(!strcmp(ifa->ifa_name, a_ping_handle->device) &&
+                        !memcmp(&((struct sockaddr_in *) ifa->ifa_addr)->sin_addr,
+                                &a_ping_handle->source.sin_addr, sizeof(a_ping_handle->source.sin_addr)))
+                    break;
+            }
+            if(ifa && !memcmp(&((struct sockaddr_in *) ifa->ifa_addr)->sin_addr,
+                    &dst.sin_addr, sizeof(a_ping_handle->source.sin_addr))) {
+                enable_capability_raw(a_ping_handle);
+                setsockopt(sock->fd, SOL_SOCKET, SO_BINDTODEVICE, "", 0);
+                disable_capability_raw(a_ping_handle);
+            }
+            freeifaddrs(ifa0);
+            if(!ifa)
+                error(0, 0, "Warning: source address might be selected on device other than: %s", a_ping_handle->device);
+        }
+        close(probe_fd);
+    }
+    while(0)
+        ;
+
+    if(a_ping_handle->whereto.sin_addr.s_addr == 0)
+        a_ping_handle->whereto.sin_addr.s_addr = a_ping_handle->source.sin_addr.s_addr;
+
+    if(a_ping_handle->device) {
+        struct ifreq ifr;
+
+        memset(&ifr, 0, sizeof(ifr));
+        strncpy(ifr.ifr_name, a_ping_handle->device, IFNAMSIZ - 1);
+        if(ioctl(sock->fd, SIOCGIFINDEX, &ifr) < 0)
+            error(2, 0, "unknown iface: %s", a_ping_handle->device);
+    }
+
+    if(a_ping_handle->broadcast_pings || IN_MULTICAST(ntohl(a_ping_handle->whereto.sin_addr.s_addr))) {
+        if(a_ping_handle->ping_common.uid) {
+            if(a_ping_handle->ping_common.interval < 1000)
+                error(2, 0, "broadcast ping with too short interval: %d", a_ping_handle->ping_common.interval);
+            if(a_ping_handle->pmtudisc >= 0 && a_ping_handle->pmtudisc != IP_PMTUDISC_DO)
+                error(2, 0, "broadcast ping does not fragment");
+        }
+        if(a_ping_handle->pmtudisc < 0)
+            a_ping_handle->pmtudisc = IP_PMTUDISC_DO;
+    }
+
+    if(a_ping_handle->pmtudisc >= 0) {
+        if(setsockopt(sock->fd, SOL_IP, IP_MTU_DISCOVER, &a_ping_handle->pmtudisc, sizeof (a_ping_handle->pmtudisc)) == -1)
+            error(2, errno, "IP_MTU_DISCOVER");
+    }
+
+    if((a_ping_handle->ping_common.options & F_STRICTSOURCE) &&
+            bind(sock->fd, (struct sockaddr *) &a_ping_handle->source, sizeof (a_ping_handle->source)) == -1)
+        error(2, errno, "bind");
+
+    if(sock->socktype == SOCK_RAW) {
+        struct icmp_filter filt;
+        filt.data = ~((1 << ICMP_SOURCE_QUENCH) |
+                (1 << ICMP_DEST_UNREACH) |
+                (1 << ICMP_TIME_EXCEEDED) |
+                (1 << ICMP_PARAMETERPROB) |
+                (1 << ICMP_REDIRECT) |
+                (1 << ICMP_ECHOREPLY));
+        if(setsockopt(sock->fd, SOL_RAW, ICMP_FILTER, &filt, sizeof filt) == -1)
+            error(0, errno, "WARNING: setsockopt(ICMP_FILTER)");
+    }
+
+    hold = 1;
+    if(setsockopt(sock->fd, SOL_IP, IP_RECVERR, &hold, sizeof hold))
+        error(0, 0, "WARNING: your kernel is veeery old. No problems.");
+
+    if(sock->socktype == SOCK_DGRAM) {
+        if(setsockopt(sock->fd, SOL_IP, IP_RECVTTL, &hold, sizeof hold))
+            error(0, errno, "WARNING: setsockopt(IP_RECVTTL)");
+        if(setsockopt(sock->fd, SOL_IP, IP_RETOPTS, &hold, sizeof hold))
+            error(0, errno, "WARNING: setsockopt(IP_RETOPTS)");
+    }
+
+    /* record route option */
+    if(a_ping_handle->ping_common.options & F_RROUTE) {
+        memset(rspace, 0, sizeof(rspace));
+        rspace[0] = IPOPT_NOP;
+        rspace[1 + IPOPT_OPTVAL] = IPOPT_RR;
+        rspace[1 + IPOPT_OLEN] = sizeof(rspace) - 1;
+        rspace[1 + IPOPT_OFFSET] = IPOPT_MINOFF;
+        a_ping_handle->optlen = 40;
+        if(setsockopt(sock->fd, IPPROTO_IP, IP_OPTIONS, rspace, sizeof rspace) < 0)
+            error(2, errno, "record route");
+    }
+    if(a_ping_handle->ping_common.options & F_TIMESTAMP) {
+        memset(rspace, 0, sizeof(rspace));
+        rspace[0] = IPOPT_TIMESTAMP;
+        rspace[1] = (a_ping_handle->ts_type == IPOPT_TS_TSONLY ? 40 : 36);
+        rspace[2] = 5;
+        rspace[3] = a_ping_handle->ts_type;
+        if(a_ping_handle->ts_type == IPOPT_TS_PRESPEC) {
+            int i;
+            rspace[1] = 4 + a_ping_handle->nroute * 8;
+            for(i = 0; i < a_ping_handle->nroute; i++) {
+                tmp_rspace = (uint32_t*) &rspace[4 + i * 8];
+                *tmp_rspace = a_ping_handle->route[i];
+            }
+        }
+        if(setsockopt(sock->fd, IPPROTO_IP, IP_OPTIONS, rspace, rspace[1]) < 0) {
+            rspace[3] = 2;
+            if(setsockopt(sock->fd, IPPROTO_IP, IP_OPTIONS, rspace, rspace[1]) < 0)
+                error(2, errno, "ts option");
+        }
+        a_ping_handle->optlen = 40;
+    }
+    if(a_ping_handle->ping_common.options & F_SOURCEROUTE) {
+        int i;
+        memset(rspace, 0, sizeof(rspace));
+        rspace[0] = IPOPT_NOOP;
+        rspace[1 + IPOPT_OPTVAL] = (a_ping_handle->ping_common.options & F_SO_DONTROUTE) ? IPOPT_SSRR
+                                                                :
+                                                                IPOPT_LSRR;
+        rspace[1 + IPOPT_OLEN] = 3 + a_ping_handle->nroute * 4;
+        rspace[1 + IPOPT_OFFSET] = IPOPT_MINOFF;
+        for(i = 0; i < a_ping_handle->nroute; i++) {
+            tmp_rspace = (uint32_t*) &rspace[4 + i * 4];
+            *tmp_rspace = a_ping_handle->route[i];
+        }
+
+        if(setsockopt(sock->fd, IPPROTO_IP, IP_OPTIONS, rspace, 4 + a_ping_handle->nroute * 4) < 0)
+            error(2, errno, "record route");
+        a_ping_handle->optlen = 40;
+    }
+
+    /* Estimate memory eaten by single packet. It is rough estimate.
+     * Actually, for small datalen's it depends on kernel side a lot. */
+    hold = a_ping_handle->ping_common.datalen + 8;
+    hold += ((hold + 511) / 512) * (a_ping_handle->optlen + 20 + 16 + 64 + 160);
+    sock_setbufs(a_ping_handle, sock, hold);
+
+    if(a_ping_handle->broadcast_pings) {
+        if(setsockopt(sock->fd, SOL_SOCKET, SO_BROADCAST, &a_ping_handle->broadcast_pings, sizeof (a_ping_handle->broadcast_pings)) < 0)
+            error(2, errno, "cannot set broadcasting");
+    }
+
+    if(a_ping_handle->ping_common.options & F_NOLOOP) {
+        int loop = 0;
+        if(setsockopt(sock->fd, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof loop) == -1)
+            error(2, errno, "cannot disable multicast loopback");
+    }
+    if(a_ping_handle->ping_common.options & F_TTL) {
+        int ittl = a_ping_handle->ping_common.ttl;
+        if(setsockopt(sock->fd, IPPROTO_IP, IP_MULTICAST_TTL, &a_ping_handle->ping_common.ttl, sizeof (a_ping_handle->ping_common.ttl)) == -1)
+            error(2, errno, "cannot set multicast time-to-live");
+        if(setsockopt(sock->fd, IPPROTO_IP, IP_TTL, &ittl, sizeof ittl) == -1)
+            error(2, errno, "cannot set unicast time-to-live");
+    }
+
+    if(a_ping_handle->ping_common.datalen > 0xFFFF - 8 - a_ping_handle->optlen - 20)
+        error(2, 0, "packet size %d is too large. Maximum is %d",
+                a_ping_handle->ping_common.datalen, 0xFFFF - 8 - 20 - a_ping_handle->optlen);
+
+    if(a_ping_handle->ping_common.datalen >= (int) sizeof(struct timeval)) /* can we time transfer */
+        a_ping_handle->ping_common.timing = 1;
+    packlen = a_ping_handle->ping_common.datalen + MAXIPLEN + MAXICMPLEN;
+    if(!(packet = (unsigned char *) malloc((unsigned int) packlen)))
+        error(2, errno, "memory allocation failed");
+
+//printf("PING %s (%s) ", hostname, inet_ntoa(whereto.sin_addr));
+    if(a_ping_handle->device || (a_ping_handle->ping_common.options & F_STRICTSOURCE))
+        printf("from %s %s: ", inet_ntoa(a_ping_handle->source.sin_addr), a_ping_handle->device ? a_ping_handle->device : "");
+//printf("%d(%d) bytes of data.\n", datalen, datalen + 8 + optlen + 20);
+
+    setup(a_ping_handle, sock);
+    log_printf("main_loop start %s (%s)\n", a_ping_handle->ping_common.hostname, inet_ntoa(a_ping_handle->whereto.sin_addr));
+    main_loop(a_ping_handle, &ping4_func_set, sock, packet, packlen);
+    log_printf("main_loop end\n");
+    return 0;
+}
+
+int ping4_receive_error_msg(ping_handle_t *a_ping_handle, socket_st *sock)
+{
+    ssize_t res;
+    char cbuf[512];
+    struct iovec iov;
+    struct msghdr msg;
+    struct cmsghdr *cmsgh;
+    struct sock_extended_err *e;
+    struct icmphdr icmph;
+    struct sockaddr_in target;
+    int net_errors = 0;
+    int local_errors = 0;
+    int saved_errno = errno;
+
+    iov.iov_base = &icmph;
+    iov.iov_len = sizeof(icmph);
+    msg.msg_name = (void*) &target;
+    msg.msg_namelen = sizeof(target);
+    msg.msg_iov = &iov;
+    msg.msg_iovlen = 1;
+    msg.msg_flags = 0;
+    msg.msg_control = cbuf;
+    msg.msg_controllen = sizeof(cbuf);
+    if(!sock)
+        return net_errors;
+    res = recvmsg(sock->fd, &msg, MSG_ERRQUEUE | MSG_DONTWAIT);
+    if(res < 0)
+        goto out;
+
+    e = NULL;
+    for(cmsgh = CMSG_FIRSTHDR(&msg); cmsgh; cmsgh = CMSG_NXTHDR(&msg, cmsgh)) {
+        if(cmsgh->cmsg_level == SOL_IP) {
+            if(cmsgh->cmsg_type == IP_RECVERR)
+                e = (struct sock_extended_err *) CMSG_DATA(cmsgh);
+        }
+    }
+    if(e == NULL)
+        abort();
+
+    if(e->ee_origin == SO_EE_ORIGIN_LOCAL) {
+        local_errors++;
+        if(a_ping_handle->ping_common.options & F_QUIET)
+            goto out;
+        if(a_ping_handle->ping_common.options & F_FLOOD)
+            write_stdout("E", 1);
+        else if(e->ee_errno != EMSGSIZE)
+            error(0, 0, "local error: %s", strerror(e->ee_errno));
+        else
+            error(0, 0, "local error: message too long, mtu=%u", e->ee_info);
+        a_ping_handle->ping_common.nerrors++;
+    } else if(e->ee_origin == SO_EE_ORIGIN_ICMP) {
+        struct sockaddr_in *sin = (struct sockaddr_in*) (e + 1);
+
+        if(res < (ssize_t) sizeof(icmph) ||
+                target.sin_addr.s_addr != a_ping_handle->whereto.sin_addr.s_addr ||
+                icmph.type != ICMP_ECHO ||
+                !is_ours(a_ping_handle, sock, icmph.un.echo.id)) {
+            /* Not our error, not an error at all. Clear. */
+            saved_errno = 0;
+            goto out;
+        }
+
+        acknowledge(a_ping_handle, ntohs(icmph.un.echo.sequence));
+
+        net_errors++;
+        a_ping_handle->ping_common.nerrors++;
+        if(a_ping_handle->ping_common.options & F_QUIET)
+            goto out;
+        if(a_ping_handle->ping_common.options & F_FLOOD) {
+            write_stdout("\bE", 2);
+        } else {
+            print_timestamp(a_ping_handle);
+            printf("From %s icmp_seq=%u ", pr_addr(a_ping_handle, sin, sizeof *sin), ntohs(icmph.un.echo.sequence));
+            pr_icmph(a_ping_handle, e->ee_type, e->ee_code, e->ee_info, NULL);
+            fflush(stdout);
+        }
+    }
+
+    out:
+    errno = saved_errno;
+    return net_errors ? net_errors : -local_errors;
+}
+
+/*
+ * pinger --
+ * 	Compose and transmit an ICMP ECHO REQUEST packet.  The IP packet
+ * will be added on by the kernel.  The ID field is our UNIX process ID,
+ * and the sequence number is an ascending integer.  The first several bytes
+ * of the data portion are used to hold a UNIX "timeval" struct in VAX
+ * byte-order, to compute the round-trip time.
+ */
+int ping4_send_probe(ping_handle_t *a_ping_handle, socket_st *sock, void *packet, unsigned packet_size __attribute__((__unused__)))
+{
+    struct icmphdr *icp;
+    int cc;
+    int i;
+
+    icp = (struct icmphdr *) packet;
+    icp->type = ICMP_ECHO;
+    icp->code = 0;
+    icp->checksum = 0;
+    icp->un.echo.sequence = htons(a_ping_handle->ping_common.ntransmitted + 1);
+    icp->un.echo.id = a_ping_handle->ping_common.ident; /* ID */
+
+    rcvd_clear(a_ping_handle, a_ping_handle->ping_common.ntransmitted + 1);
+
+    if(a_ping_handle->ping_common.timing) {
+        if(a_ping_handle->ping_common.options & F_LATENCY) {
+            struct timeval tmp_tv;
+            gettimeofday(&tmp_tv, NULL);
+            memcpy(icp + 1, &tmp_tv, sizeof(tmp_tv));
+        } else {
+            memset(icp + 1, 0, sizeof(struct timeval));
+        }
+    }
+
+    cc = a_ping_handle->ping_common.datalen + 8; /* skips ICMP portion */
+
+    /* compute ICMP checksum here */
+    icp->checksum = in_cksum((unsigned short *) icp, cc, 0);
+
+    if(a_ping_handle->ping_common.timing && !(a_ping_handle->ping_common.options & F_LATENCY)) {
+        struct timeval tmp_tv;
+        gettimeofday(&tmp_tv, NULL);
+        memcpy(icp + 1, &tmp_tv, sizeof(tmp_tv));
+        icp->checksum = in_cksum((unsigned short *) &tmp_tv, sizeof(tmp_tv), ~icp->checksum);
+    }
+
+    i = sendto(sock->fd, icp, cc, 0, (struct sockaddr*) &a_ping_handle->whereto, sizeof(a_ping_handle->whereto));
+    //log_printf("**sendto(fd=%d,icp=0x%x,cc=%d)=%d\n",sock->fd,&icp,cc,i);
+    return (cc == i ? 0 : i);
+}
+
+/*
+ * parse_reply --
+ *	Print out the packet, if it came from us.  This logic is necessary
+ * because ALL readers of the ICMP socket get a copy of ALL ICMP packets
+ * which arrive ('tis only fair).  This permits multiple copies of this
+ * program to be run without having intermingled output (or statistics!).
+ */
+static
+void pr_echo_reply(uint8_t *_icp, int len __attribute__((__unused__)))
+{
+    struct icmphdr *icp = (struct icmphdr *) _icp;
+    log_printf(" icmp_seq=%u", ntohs(icp->un.echo.sequence));
+}
+
+int
+ping4_parse_reply(ping_handle_t *a_ping_handle, struct socket_st *sock, struct msghdr *msg, int cc, void *addr, struct timeval *tv)
+{
+    struct sockaddr_in *from = addr;
+    uint8_t *buf = msg->msg_iov->iov_base;
+    struct icmphdr *icp;
+    struct iphdr *ip;
+    int hlen;
+    int csfailed;
+    struct cmsghdr *cmsgh;
+    int reply_ttl;
+    uint8_t *opts, *tmp_ttl;
+    int olen;
+
+    /* Check the IP header */
+    ip = (struct iphdr *) buf;
+    if(sock->socktype == SOCK_RAW) {
+        hlen = ip->ihl * 4;
+        if(cc < hlen + 8 || ip->ihl < 5) {
+            if(a_ping_handle->ping_common.options & F_VERBOSE)
+                error(0, 0, "packet too short (%d bytes) from %s", cc,
+                        pr_addr(a_ping_handle, from, sizeof *from));
+            return 1;
+        }
+        reply_ttl = ip->ttl;
+        opts = buf + sizeof(struct iphdr);
+        olen = hlen - sizeof(struct iphdr);
+    } else {
+        hlen = 0;
+        reply_ttl = 0;
+        opts = buf;
+        olen = 0;
+        for(cmsgh = CMSG_FIRSTHDR(msg); cmsgh; cmsgh = CMSG_NXTHDR(msg, cmsgh)) {
+            if(cmsgh->cmsg_level != SOL_IP)
+                continue;
+            if(cmsgh->cmsg_type == IP_TTL) {
+                if(cmsgh->cmsg_len < sizeof(int))
+                    continue;
+                tmp_ttl = (uint8_t *) CMSG_DATA(cmsgh);
+                reply_ttl = (int) *tmp_ttl;
+            } else if(cmsgh->cmsg_type == IP_RETOPTS) {
+                opts = (uint8_t *) CMSG_DATA(cmsgh);
+                olen = cmsgh->cmsg_len;
+            }
+        }
+    }
+
+    /* Now the ICMP part */
+    cc -= hlen;
+    icp = (struct icmphdr *) (buf + hlen);
+    csfailed = in_cksum((unsigned short *) icp, cc, 0);
+
+    if(icp->type == ICMP_ECHOREPLY) {
+        //log_printf("in ping4_parse_reply00\n");
+        if(!is_ours(a_ping_handle, sock, icp->un.echo.id))
+            return 1; /* 'Twas not our ECHO */
+        if(!contains_pattern_in_payload(a_ping_handle, (uint8_t*) (icp + 1)))
+            return 1; /* 'Twas really not our ECHO */
+        if(gather_statistics(a_ping_handle, (uint8_t*) icp, sizeof(*icp), cc,
+                ntohs(icp->un.echo.sequence),
+                reply_ttl, 0, tv, pr_addr(a_ping_handle, from, sizeof *from),
+                pr_echo_reply)) {
+            fflush(stdout);
+            return 0;
+        }
+        //log_printf("in ping4_parse_reply01\n");
+    } else {
+        /* We fall here when a redirect or source quench arrived. */
+        switch (icp->type) {
+        case ICMP_ECHO:
+            /* MUST NOT */
+            return 1;
+        case ICMP_SOURCE_QUENCH:
+            case ICMP_REDIRECT:
+            case ICMP_DEST_UNREACH:
+            case ICMP_TIME_EXCEEDED:
+            case ICMP_PARAMETERPROB:
+            {
+            struct iphdr * iph = (struct iphdr *) (&icp[1]);
+            struct icmphdr *icp1 = (struct icmphdr*) ((unsigned char *) iph + iph->ihl * 4);
+            int error_pkt;
+            if(cc < (int) (8 + sizeof(struct iphdr) + 8) ||
+                    cc < 8 + iph->ihl * 4 + 8)
+                return 1;
+            if(icp1->type != ICMP_ECHO ||
+                    iph->daddr != a_ping_handle->whereto.sin_addr.s_addr ||
+                    !is_ours(a_ping_handle, sock, icp1->un.echo.id))
+                return 1;
+            error_pkt = (icp->type != ICMP_REDIRECT &&
+                    icp->type != ICMP_SOURCE_QUENCH);
+            if(error_pkt) {
+                acknowledge(a_ping_handle, ntohs(icp1->un.echo.sequence));
+                return 0;
+            }
+            if(a_ping_handle->ping_common.options & (F_QUIET | F_FLOOD))
+                return 1;
+            print_timestamp(a_ping_handle);
+            log_printf("From %s: icmp_seq=%u ",
+                    pr_addr(a_ping_handle, from, sizeof *from),
+                    ntohs(icp1->un.echo.sequence));
+            if(csfailed)
+                log_printf("(BAD CHECKSUM)");
+            pr_icmph(a_ping_handle, icp->type, icp->code, ntohl(icp->un.gateway), icp);
+            return 1;
+        }
+        default:
+            /* MUST NOT */
+            break;
+        }
+        if((a_ping_handle->ping_common.options & F_FLOOD) && !(a_ping_handle->ping_common.options & (F_VERBOSE | F_QUIET))) {
+            if(!csfailed)
+                write_stdout("!E", 2);
+            else
+                write_stdout("!EC", 3);
+            return 0;
+        }
+        if(!(a_ping_handle->ping_common.options & F_VERBOSE) || a_ping_handle->ping_common.uid)
+            return 0;
+        if(a_ping_handle->ping_common.options & F_PTIMEOFDAY) {
+            struct timeval recv_time;
+            gettimeofday(&recv_time, NULL);
+            log_printf("%lu.%06lu ", (unsigned long) recv_time.tv_sec, (unsigned long) recv_time.tv_usec);
+        }
+        printf("From %s: ", pr_addr(a_ping_handle, from, sizeof *from));
+        if(csfailed) {
+            log_printf("(BAD CHECKSUM)\n");
+            return 0;
+        }
+        pr_icmph(a_ping_handle, icp->type, icp->code, ntohl(icp->un.gateway), icp);
+        return 0;
+    }
+
+    if(a_ping_handle->ping_common.options & F_AUDIBLE) {
+        log_printf("\a"); //putchar('\a');
+        if(a_ping_handle->ping_common.options & F_FLOOD)
+            fflush(stdout);
+    }
+    if(!(a_ping_handle->ping_common.options & F_FLOOD)) {
+        pr_options(a_ping_handle, opts, olen + sizeof(struct iphdr));
+
+        log_printf("\n"); //putchar('\n');
+        fflush(stdout);
+    }
+    return 0;
+}
+
+#if BYTE_ORDER == LITTLE_ENDIAN
+# define ODDBYTE(v)	(v)
+#elif BYTE_ORDER == BIG_ENDIAN
+# define ODDBYTE(v)	((unsigned short)(v) << 8)
+#else
+# define ODDBYTE(v)	htons((unsigned short)(v) << 8)
+#endif
+
+unsigned short
+in_cksum(const unsigned short *addr, int len, unsigned short csum)
+{
+    int nleft = len;
+    const unsigned short *w = addr;
+    unsigned short answer;
+    int sum = csum;
+
+    /*
+     *  Our algorithm is simple, using a 32 bit accumulator (sum),
+     *  we add sequential 16 bit words to it, and at the end, fold
+     *  back all the carry bits from the top 16 bits into the lower
+     *  16 bits.
+     */
+    while(nleft > 1) {
+        sum += *w++;
+        nleft -= 2;
+    }
+
+    /* mop up an odd byte, if necessary */
+    if(nleft == 1)
+        sum += ODDBYTE(*(unsigned char * )w); /* le16toh() may be unavailable on old systems */
+
+    /*
+     * add back carry outs from top 16 bits to low 16 bits
+     */
+    sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
+    sum += (sum >> 16); /* add carry */
+    answer = ~sum; /* truncate to 16 bits */
+    return (answer);
+}
+
+/*
+ * pr_icmph --
+ *	Print a descriptive string about an ICMP header.
+ */
+void pr_icmph(ping_handle_t *a_ping_handle, uint8_t type, uint8_t code, uint32_t info, struct icmphdr *icp)
+{
+    switch (type) {
+    case ICMP_ECHOREPLY:
+        printf("Echo Reply\n");
+        /* XXX ID + Seq + Data */
+        break;
+    case ICMP_DEST_UNREACH:
+        switch (code) {
+        case ICMP_NET_UNREACH:
+            printf("Destination Net Unreachable\n");
+            break;
+        case ICMP_HOST_UNREACH:
+            printf("Destination Host Unreachable\n");
+            break;
+        case ICMP_PROT_UNREACH:
+            printf("Destination Protocol Unreachable\n");
+            break;
+        case ICMP_PORT_UNREACH:
+            printf("Destination Port Unreachable\n");
+            break;
+        case ICMP_FRAG_NEEDED:
+            printf("Frag needed and DF set (mtu = %u)\n", info);
+            break;
+        case ICMP_SR_FAILED:
+            printf("Source Route Failed\n");
+            break;
+        case ICMP_NET_UNKNOWN:
+            printf("Destination Net Unknown\n");
+            break;
+        case ICMP_HOST_UNKNOWN:
+            printf("Destination Host Unknown\n");
+            break;
+        case ICMP_HOST_ISOLATED:
+            printf("Source Host Isolated\n");
+            break;
+        case ICMP_NET_ANO:
+            printf("Destination Net Prohibited\n");
+            break;
+        case ICMP_HOST_ANO:
+            printf("Destination Host Prohibited\n");
+            break;
+        case ICMP_NET_UNR_TOS:
+            printf("Destination Net Unreachable for Type of Service\n");
+            break;
+        case ICMP_HOST_UNR_TOS:
+            printf("Destination Host Unreachable for Type of Service\n");
+            break;
+        case ICMP_PKT_FILTERED:
+            printf("Packet filtered\n");
+            break;
+        case ICMP_PREC_VIOLATION:
+            printf("Precedence Violation\n");
+            break;
+        case ICMP_PREC_CUTOFF:
+            printf("Precedence Cutoff\n");
+            break;
+        default:
+            printf("Dest Unreachable, Bad Code: %d\n", code);
+            break;
+        }
+        if(icp && (a_ping_handle->ping_common.options & F_VERBOSE))
+            pr_iph(a_ping_handle, (struct iphdr*) (icp + 1));
+        break;
+    case ICMP_SOURCE_QUENCH:
+        printf("Source Quench\n");
+        if(icp && (a_ping_handle->ping_common.options & F_VERBOSE))
+            pr_iph(a_ping_handle, (struct iphdr*) (icp + 1));
+        break;
+    case ICMP_REDIRECT:
+        switch (code) {
+        case ICMP_REDIR_NET:
+            printf("Redirect Network");
+            break;
+        case ICMP_REDIR_HOST:
+            printf("Redirect Host");
+            break;
+        case ICMP_REDIR_NETTOS:
+            printf("Redirect Type of Service and Network");
+            break;
+        case ICMP_REDIR_HOSTTOS:
+            printf("Redirect Type of Service and Host");
+            break;
+        default:
+            printf("Redirect, Bad Code: %d", code);
+            break;
+        }
+        {
+            struct sockaddr_in sin = { .sin_family = AF_INET, .sin_addr = { icp ? icp->un.gateway : info } };
+
+            printf("(New nexthop: %s)\n", pr_addr(a_ping_handle, &sin, sizeof sin));
+        }
+        if(icp && (a_ping_handle->ping_common.options & F_VERBOSE))
+            pr_iph(a_ping_handle, (struct iphdr*) (icp + 1));
+        break;
+    case ICMP_ECHO:
+        printf("Echo Request\n");
+        /* XXX ID + Seq + Data */
+        break;
+    case ICMP_TIME_EXCEEDED:
+        switch (code) {
+        case ICMP_EXC_TTL:
+            printf("Time to live exceeded\n");
+            break;
+        case ICMP_EXC_FRAGTIME:
+            printf("Frag reassembly time exceeded\n");
+            break;
+        default:
+            printf("Time exceeded, Bad Code: %d\n", code);
+            break;
+        }
+        if(icp && (a_ping_handle->ping_common.options & F_VERBOSE))
+            pr_iph(a_ping_handle, (struct iphdr*) (icp + 1));
+        break;
+    case ICMP_PARAMETERPROB:
+        printf("Parameter problem: pointer = %u\n", icp ? (ntohl(icp->un.gateway) >> 24) : info);
+        if(icp && (a_ping_handle->ping_common.options & F_VERBOSE))
+            pr_iph(a_ping_handle, (struct iphdr*) (icp + 1));
+        break;
+    case ICMP_TIMESTAMP:
+        printf("Timestamp\n");
+        /* XXX ID + Seq + 3 timestamps */
+        break;
+    case ICMP_TIMESTAMPREPLY:
+        printf("Timestamp Reply\n");
+        /* XXX ID + Seq + 3 timestamps */
+        break;
+    case ICMP_INFO_REQUEST:
+        printf("Information Request\n");
+        /* XXX ID + Seq */
+        break;
+    case ICMP_INFO_REPLY:
+        printf("Information Reply\n");
+        /* XXX ID + Seq */
+        break;
+#ifdef ICMP_MASKREQ
+    case ICMP_MASKREQ:
+        printf("Address Mask Request\n");
+        break;
+#endif
+#ifdef ICMP_MASKREPLY
+    case ICMP_MASKREPLY:
+        printf("Address Mask Reply\n");
+        break;
+#endif
+    default:
+        printf("Bad ICMP type: %d\n", type);
+    }
+}
+
+void pr_options(ping_handle_t *a_ping_handle, unsigned char * cp, int hlen)
+{
+    int i, j;
+    int olen, totlen;
+    unsigned char * optptr;
+    static int old_rrlen;
+    static char old_rr[MAX_IPOPTLEN];
+
+    totlen = hlen - sizeof(struct iphdr);
+    optptr = cp;
+
+    while(totlen > 0) {
+        if(*optptr == IPOPT_EOL)
+            break;
+        if(*optptr == IPOPT_NOP) {
+            totlen--;
+            optptr++;
+            printf("\nNOP");
+            continue;
+        }
+        cp = optptr;
+        olen = optptr[1];
+        if(olen < 2 || olen > totlen)
+            break;
+
+        switch (*cp) {
+        case IPOPT_SSRR:
+            case IPOPT_LSRR:
+            printf("\n%cSRR: ", *cp == IPOPT_SSRR ? 'S' : 'L');
+            j = *++cp;
+            cp++;
+            if(j > IPOPT_MINOFF) {
+                for(;;) {
+                    uint32_t address;
+                    memcpy(&address, cp, 4);
+                    cp += 4;
+                    if(address == 0)
+                        printf("\t0.0.0.0");
+                    else {
+                        struct sockaddr_in sin = { .sin_family = AF_INET, .sin_addr = { address } };
+
+                        printf("\t%s", pr_addr(a_ping_handle, &sin, sizeof sin));
+                    }
+                    j -= 4;
+                    putchar('\n');
+                    if(j <= IPOPT_MINOFF)
+                        break;
+                }
+            }
+            break;
+        case IPOPT_RR:
+            j = *++cp; /* get length */
+            i = *++cp; /* and pointer */
+            if(i > j)
+                i = j;
+            i -= IPOPT_MINOFF;
+            if(i <= 0)
+                break;
+            if(i == old_rrlen
+                    && !memcmp(cp, old_rr, i)
+                    && !(a_ping_handle->ping_common.options & F_FLOOD)) {
+                printf("\t(same route)");
+                break;
+            }
+            old_rrlen = i;
+            memcpy(old_rr, (char *) cp, i);
+            printf("\nRR: ");
+            cp++;
+            for(;;) {
+                uint32_t address;
+                memcpy(&address, cp, 4);
+                cp += 4;
+                if(address == 0)
+                    printf("\t0.0.0.0");
+                else {
+                    struct sockaddr_in sin = { .sin_family = AF_INET, .sin_addr = { address } };
+
+                    printf("\t%s", pr_addr(a_ping_handle, &sin, sizeof sin));
+                }
+                i -= 4;
+                putchar('\n');
+                if(i <= 0)
+                    break;
+            }
+            break;
+        case IPOPT_TS:
+            {
+            int stdtime = 0, nonstdtime = 0;
+            uint8_t flags;
+            j = *++cp; /* get length */
+            i = *++cp; /* and pointer */
+            if(i > j)
+                i = j;
+            i -= 5;
+            if(i <= 0)
+                break;
+            flags = *++cp;
+            printf("\nTS: ");
+            cp++;
+            for(;;) {
+                long l;
+
+                if((flags & 0xF) != IPOPT_TS_TSONLY) {
+                    uint32_t address;
+                    memcpy(&address, cp, 4);
+                    cp += 4;
+                    if(address == 0)
+                        printf("\t0.0.0.0");
+                    else {
+                        struct sockaddr_in sin = { .sin_family = AF_INET, .sin_addr = { address } };
+
+                        printf("\t%s", pr_addr(a_ping_handle, &sin, sizeof sin));
+                    }
+                    i -= 4;
+                    if(i <= 0)
+                        break;
+                }
+                l = *cp++;
+                l = (l << 8) + *cp++;
+                l = (l << 8) + *cp++;
+                l = (l << 8) + *cp++;
+
+                if(l & 0x80000000) {
+                    if(nonstdtime == 0)
+                        printf("\t%ld absolute not-standard", l & 0x7fffffff);
+                    else
+                        printf("\t%ld not-standard", (l & 0x7fffffff) - nonstdtime);
+                    nonstdtime = l & 0x7fffffff;
+                } else {
+                    if(stdtime == 0)
+                        printf("\t%ld absolute", l);
+                    else
+                        printf("\t%ld", l - stdtime);
+                    stdtime = l;
+                }
+                i -= 4;
+                putchar('\n');
+                if(i <= 0)
+                    break;
+            }
+            if(flags >> 4)
+                printf("Unrecorded hops: %d\n", flags >> 4);
+            break;
+        }
+        default:
+            printf("\nunknown option %x", *cp);
+            break;
+        }
+        totlen -= olen;
+        optptr += olen;
+    }
+}
+
+/*
+ * pr_iph --
+ *	Print an IP header with options.
+ */
+void pr_iph(ping_handle_t *a_ping_handle, struct iphdr *ip)
+{
+    int hlen;
+    unsigned char *cp;
+
+    hlen = ip->ihl << 2;
+    cp = (unsigned char *) ip + 20; /* point to options */
+
+    printf("Vr HL TOS  Len   ID Flg  off TTL Pro  cks      Src      Dst Data\n");
+    printf(" %1x  %1x  %02x %04x %04x",
+            ip->version, ip->ihl, ip->tos, ip->tot_len, ip->id);
+    printf("   %1x %04x", ((ip->frag_off) & 0xe000) >> 13,
+            (ip->frag_off) & 0x1fff);
+    printf("  %02x  %02x %04x", ip->ttl, ip->protocol, ip->check);
+    printf(" %s ", inet_ntoa(*(struct in_addr *) &ip->saddr));
+    printf(" %s ", inet_ntoa(*(struct in_addr *) &ip->daddr));
+    printf("\n");
+    pr_options(a_ping_handle, cp, hlen);
+}
+
+/*
+ * pr_addr --
+ *
+ * Return an ascii host address optionally with a hostname.
+ */
+char *
+pr_addr(ping_handle_t *a_ping_handle, void *sa, socklen_t salen)
+{
+    static char buffer[4096] = "";
+    static struct sockaddr_storage last_sa = { 0, { 0 }, 0 };
+    static socklen_t last_salen = 0;
+    char name[NI_MAXHOST] = "";
+    char address[NI_MAXHOST] = "";
+
+    if(salen == last_salen && !memcmp(sa, &last_sa, salen))
+        return buffer;
+
+    memcpy(&last_sa, sa, (last_salen = salen));
+
+    a_ping_handle->ping_common.in_pr_addr = !setjmp(a_ping_handle->ping_common.pr_addr_jmp);
+
+    getnameinfo(sa, salen, address, sizeof address, NULL, 0, getnameinfo_flags | NI_NUMERICHOST);
+    if(!exiting && !(a_ping_handle->ping_common.options & F_NUMERIC))
+        getnameinfo(sa, salen, name, sizeof name, NULL, 0, getnameinfo_flags);
+
+    if(*name)
+        snprintf(buffer, sizeof buffer, "%s (%s)", name, address);
+    else
+        snprintf(buffer, sizeof buffer, "%s", address);
+
+    a_ping_handle->ping_common.in_pr_addr = 0;
+
+    return (buffer);
+}
+
+/* Set Type of Service (TOS) and other Quality of Service relating bits */
+int parsetos(char *str)
+{
+    const char *cp;
+    int tos;
+    char *ep;
+
+    /* handle both hex and decimal values */
+    if(str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) {
+        cp = str + 2;
+        tos = (int) strtol(cp, &ep, 16);
+    } else
+        tos = (int) strtol(str, &ep, 10);
+
+    /* doesn't look like decimal or hex, eh? */
+    if(*ep != '\0')
+        error(2, 0, "bad TOS value: %s", str);
+
+    if(tos > TOS_MAX)
+        error(2, 0, "the decimal value of TOS bits must be in range 0-255: %d", tos);
+    return (tos);
+}
+
+int parseflow(char *str)
+{
+    const char *cp;
+    unsigned long val;
+    char *ep;
+
+    /* handle both hex and decimal values */
+    if(str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) {
+        cp = str + 2;
+        val = (int) strtoul(cp, &ep, 16);
+    } else
+        val = (int) strtoul(str, &ep, 10);
+
+    /* doesn't look like decimal or hex, eh? */
+    if(*ep != '\0')
+        error(2, 0, "bad value for flowinfo: %s", str);
+
+    if(val & ~IPV6_FLOWINFO_FLOWLABEL)
+        error(2, 0, "flow value is greater than 20 bits: %s", str);
+    return (val);
+}
+
+void ping4_install_filter(ping_handle_t *a_ping_handle, socket_st *sock)
+{
+    static int once;
+    static struct sock_filter insns[] = {
+    BPF_STMT(BPF_LDX|BPF_B|BPF_MSH, 0), /* Skip IP header. F..g BSD... Look into ping6. */
+    BPF_STMT(BPF_LD|BPF_H|BPF_IND, 4), /* Load icmp echo ident */
+    BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, 0xAAAA, 0, 1), /* Ours? */
+    BPF_STMT(BPF_RET|BPF_K, ~0U), /* Yes, it passes. */
+    BPF_STMT(BPF_LD|BPF_B|BPF_IND, 0), /* Load icmp type */
+    BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, ICMP_ECHOREPLY, 1, 0), /* Echo? */
+    BPF_STMT(BPF_RET|BPF_K, 0xFFFFFFF), /* No. It passes. */
+    BPF_STMT(BPF_RET|BPF_K, 0) /* Echo with wrong ident. Reject. */
+    };
+    static struct sock_fprog filter = {
+        sizeof insns / sizeof(insns[0]),
+        insns
+    };
+
+    if(once)
+        return;
+    once = 1;
+
+    /* Patch bpflet for current identifier. */
+    insns[2] = (struct sock_filter )BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, htons(a_ping_handle->ping_common.ident), 0, 1);
+
+    if(setsockopt(sock->fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)))
+        error(0, errno, "WARNING: failed to install socket filter");
+}
diff --git a/libdap-chain-net/iputils/ping.h b/libdap-chain-net/iputils/ping.h
new file mode 100644
index 0000000000000000000000000000000000000000..651455289f18f42a956cd9cfc8cf61d4f059ecfa
--- /dev/null
+++ b/libdap-chain-net/iputils/ping.h
@@ -0,0 +1,436 @@
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+#include <signal.h>
+#include <poll.h>
+
+#include <sys/param.h>
+#include <sys/socket.h>
+
+#define __USE_GNU
+
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/icmp6.h>
+
+#include <linux/filter.h>
+#include <linux/types.h>
+#include <linux/sockios.h>
+
+#include <sys/file.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <sys/uio.h>
+#include <ctype.h>
+#include <errno.h>
+#include <string.h>
+#include <netdb.h>
+#include <setjmp.h>
+
+#include <asm/byteorder.h>
+#include <sched.h>
+#include <math.h>
+
+#include <resolv.h>
+
+#include "iputils.h"
+
+#ifdef HAVE_ERROR_H
+#include <error.h>
+#else
+#include <stdarg.h>
+#endif
+
+#ifdef HAVE_LIBCAP
+#include <sys/prctl.h>
+#include <sys/capability.h>
+#endif
+
+#ifdef USE_IDN
+#include <locale.h>
+#include <idn2.h>
+
+#ifndef AI_IDN
+#define AI_IDN 0x0040
+#endif
+#ifndef AI_CANONIDN
+#define AI_CANONIDN 0x0080
+#endif
+#ifndef NI_IDN
+#define NI_IDN 32
+#endif
+
+#define getaddrinfo_flags (AI_CANONNAME | AI_IDN | AI_CANONIDN)
+#define getnameinfo_flags NI_IDN
+#else
+#define getaddrinfo_flags (AI_CANONNAME)
+#define getnameinfo_flags 0
+#endif
+
+#include <ifaddrs.h>
+#include <netinet/in.h>
+#include <linux/ipv6.h>
+#include <arpa/inet.h>
+#include <linux/types.h>
+#include <linux/errqueue.h>
+#include <linux/in6.h>
+
+#ifndef SCOPE_DELIMITER
+#define SCOPE_DELIMITER '%'
+#endif
+
+#define DEFDATALEN  (64 - 8)  /* default data length */
+
+#define MAXWAIT   10    /* max seconds to wait for response */
+#define MININTERVAL 10    /* Minimal interpacket gap */
+#define MINUSERINTERVAL 200   /* Minimal allowed interval for non-root */
+
+#define SCHINT(a) (((a) <= MININTERVAL) ? MININTERVAL : (a))
+
+/* various options */
+//extern int options;
+#define F_FLOOD   0x001
+#define F_INTERVAL  0x002
+#define F_NUMERIC 0x004
+#define F_PINGFILLED  0x008
+#define F_QUIET   0x010
+#define F_RROUTE  0x020
+#define F_SO_DEBUG  0x040
+#define F_SO_DONTROUTE  0x080
+#define F_VERBOSE 0x100
+#define F_TIMESTAMP 0x200
+#define F_SOURCEROUTE 0x400
+#define F_FLOOD_POLL  0x800
+#define F_LATENCY 0x1000
+#define F_AUDIBLE 0x2000
+#define F_ADAPTIVE  0x4000
+#define F_STRICTSOURCE  0x8000
+#define F_NOLOOP  0x10000
+#define F_TTL   0x20000
+#define F_MARK    0x40000
+#define F_PTIMEOFDAY  0x80000
+#define F_OUTSTANDING 0x100000
+#define F_FLOWINFO  0x200000
+#define F_TCLASS  0x400000
+
+/*
+ * MAX_DUP_CHK is the number of bits in received table, i.e. the maximum
+ * number of received sequence numbers we can keep track of.
+ */
+
+
+#if defined(__WORDSIZE) && __WORDSIZE == 64
+# define USE_BITMAP64
+#endif
+
+
+
+#if ((MAX_DUP_CHK >> (BITMAP_SHIFT + 3)) << (BITMAP_SHIFT + 3)) != MAX_DUP_CHK
+# error Please MAX_DUP_CHK and/or BITMAP_SHIFT
+#endif
+
+
+
+//extern struct rcvd_table rcvd_tbl;
+
+//#define A(a_ping_handle, bit)  (a_ping_handle->ping_common.rcvd_tbl.bitmap[(bit) >> BITMAP_SHIFT])  /* identify word in array */
+#define B(bit)  (((bitmap_t)1) << ((bit) & ((1 << BITMAP_SHIFT) - 1)))  /* identify bit in word */
+
+static inline void rcvd_set(ping_handle_t *a_ping_handle, uint16_t seq)
+{
+  unsigned bit = seq % MAX_DUP_CHK;
+  a_ping_handle->ping_common.rcvd_tbl.bitmap[(bit) >> BITMAP_SHIFT] |= B(bit);
+  //A(a_ping_handle, bit) |= B(bit);
+}
+
+static inline void rcvd_clear(ping_handle_t *a_ping_handle, uint16_t seq)
+{
+  unsigned bit = seq % MAX_DUP_CHK;
+  a_ping_handle->ping_common.rcvd_tbl.bitmap[(bit) >> BITMAP_SHIFT] &= ~B(bit);
+  //A(a_ping_handle, bit) &= ~B(bit);
+}
+
+static inline bitmap_t rcvd_test(ping_handle_t *a_ping_handle, uint16_t seq)
+{
+  unsigned bit = seq % MAX_DUP_CHK;
+  return (a_ping_handle->ping_common.rcvd_tbl.bitmap[(bit) >> BITMAP_SHIFT] & B(bit));
+  //return A(a_ping_handle, bit) & B(bit);
+}
+
+#ifndef HAVE_ERROR_H
+static void error(int status, int errnum, const char *format, ...)
+{
+  va_list ap;
+
+  fprintf(stderr, "ping: ");
+  va_start(ap, format);
+  vfprintf(stderr, format, ap);
+  va_end(ap);
+  if (errnum)
+    fprintf(stderr, ": %s\n", strerror(errnum));
+  else
+    fprintf(stderr, "\n");
+  if (status)
+    exit(status);
+}
+#endif
+
+/*
+extern int datalen;
+extern char *hostname;
+extern int uid;
+extern int ident;     // process id to identify our packets
+
+extern int sndbuf;
+extern int ttl;
+
+extern long npackets;     // max packets to transmit
+extern long nreceived;      // # of packets we got back
+extern long nrepeats;     // number of duplicates
+extern long ntransmitted;   // sequence # for outbound packets = #sent
+extern long nchecksum;      // replies with bad checksum
+extern long nerrors;      // icmp errors
+extern int interval;      // interval between packets (msec)
+extern int preload;
+extern int deadline;      // time to die
+extern int lingertime;
+extern struct timeval start_time, cur_time;
+extern int confirm;
+extern int confirm_flag;
+extern char *device;
+extern int pmtudisc;
+
+extern volatile int in_pr_addr;   // pr_addr() is executing
+extern jmp_buf pr_addr_jmp;
+*/
+
+extern volatile int exiting;
+extern volatile int status_snapshot;
+
+#ifndef MSG_CONFIRM
+#define MSG_CONFIRM 0
+#endif
+
+
+/* timing */
+/*
+extern int timing;      // flag to do timing
+extern long tmin;     // minimum round trip time
+extern long tmax;     // maximum round trip time
+extern long long tsum;      // sum of all times, for doing average
+extern long long tsum2;
+extern int rtt;
+extern uint16_t acked;
+extern int pipesize;
+*/
+
+/*
+ * Write to stdout
+ */
+static inline void write_stdout(const char *str, size_t len)
+{
+  size_t o = 0;
+  ssize_t cc;
+  do {
+    cc = write(STDOUT_FILENO, str + o, len - o);
+    o += cc;
+  } while (len > o || cc < 0);
+}
+
+/*
+ * tvsub --
+ *  Subtract 2 timeval structs:  out = out - in.  Out is assumed to
+ * be >= in.
+ */
+static inline void tvsub(struct timeval *out, struct timeval *in)
+{
+  if ((out->tv_usec -= in->tv_usec) < 0) {
+    --out->tv_sec;
+    out->tv_usec += 1000000;
+  }
+  out->tv_sec -= in->tv_sec;
+}
+
+static inline void set_signal(int signo, void (*handler)(int))
+{
+  struct sigaction sa;
+
+  memset(&sa, 0, sizeof(sa));
+
+  sa.sa_handler = (void (*)(int))handler;
+  sigaction(signo, &sa, NULL);
+}
+
+extern int __schedule_exit(ping_handle_t *a_ping_handle, int next);
+
+static inline int schedule_exit(ping_handle_t  *a_ping_handle, int next)
+{
+  if (a_ping_handle->ping_common.npackets && a_ping_handle->ping_common.ntransmitted >= a_ping_handle->ping_common.npackets
+          && !a_ping_handle->ping_common.deadline)
+    next = __schedule_exit(a_ping_handle, next);
+  return next;
+}
+
+static inline int in_flight(ping_handle_t *a_ping_handle)
+{
+  uint16_t diff = (uint16_t)a_ping_handle->ping_common.ntransmitted - a_ping_handle->ping_common.acked;
+  return (diff<=0x7FFF) ? diff : a_ping_handle->ping_common.ntransmitted-a_ping_handle->ping_common.nreceived-a_ping_handle->ping_common.nerrors;
+}
+
+static inline void acknowledge(ping_handle_t *a_ping_handle, uint16_t seq)
+{
+  uint16_t diff = (uint16_t)a_ping_handle->ping_common.ntransmitted - seq;
+  if (diff <= 0x7FFF) {
+    if ((int)diff+1 > a_ping_handle->ping_common.pipesize)
+        a_ping_handle->ping_common.pipesize = (int)diff+1;
+    if ((int16_t)(seq - a_ping_handle->ping_common.acked) > 0 ||
+        (uint16_t)a_ping_handle->ping_common.ntransmitted - a_ping_handle->ping_common.acked > 0x7FFF)
+        a_ping_handle->ping_common.acked = seq;
+  }
+}
+
+static inline void advance_ntransmitted(ping_handle_t  *a_ping_handle)
+{
+    a_ping_handle->ping_common.ntransmitted++;
+  /* Invalidate acked, if 16 bit seq overflows. */
+  if ((uint16_t)a_ping_handle->ping_common.ntransmitted - a_ping_handle->ping_common.acked > 0x7FFF)
+      a_ping_handle->ping_common.acked = (uint16_t)a_ping_handle->ping_common.ntransmitted + 1;
+}
+
+extern void usage(void) __attribute__((noreturn));
+extern void limit_capabilities(ping_handle_t *a_ping_handle);
+static int enable_capability_raw(ping_handle_t *a_ping_handle);
+static int disable_capability_raw(ping_handle_t *a_ping_handle);
+static int enable_capability_admin(ping_handle_t *a_ping_handle);
+static int disable_capability_admin(ping_handle_t *a_ping_handle);
+#ifdef HAVE_LIBCAP
+extern int modify_capability(cap_value_t, cap_flag_value_t);
+static inline int enable_capability_raw(void)   { return modify_capability(CAP_NET_RAW,   CAP_SET);   }
+static inline int disable_capability_raw(void)    { return modify_capability(CAP_NET_RAW,   CAP_CLEAR); }
+static inline int enable_capability_admin(void)   { return modify_capability(CAP_NET_ADMIN, CAP_SET);   }
+static inline int disable_capability_admin(void)  { return modify_capability(CAP_NET_ADMIN, CAP_CLEAR); }
+#else
+extern int modify_capability(ping_handle_t *a_ping_handle, int);
+static inline int enable_capability_raw(ping_handle_t *a_ping_handle)     { return modify_capability(a_ping_handle, 1); }
+static inline int disable_capability_raw(ping_handle_t *a_ping_handle)    { return modify_capability(a_ping_handle, 0); }
+static inline int enable_capability_admin(ping_handle_t *a_ping_handle)   { return modify_capability(a_ping_handle, 1); }
+static inline int disable_capability_admin(ping_handle_t *a_ping_handle)  { return modify_capability(a_ping_handle, 0); }
+#endif
+extern void drop_capabilities(void);
+
+typedef struct socket_st {
+  int fd;
+  int socktype;
+} socket_st;
+
+char *pr_addr(ping_handle_t *a_ping_handle, void *sa, socklen_t salen);
+
+int is_ours(ping_handle_t *a_ping_handle, socket_st *sock, uint16_t id);
+
+int ping4_run(ping_handle_t *a_ping_handle, int argc, char **argv, struct addrinfo *ai, socket_st *sock);
+int ping4_send_probe(ping_handle_t *a_ping_handle, socket_st *, void *packet, unsigned packet_size);
+int ping4_receive_error_msg(ping_handle_t *a_ping_handle, socket_st *sock);
+int ping4_parse_reply(ping_handle_t *a_ping_handle, socket_st *, struct msghdr *msg, int len, void *addr, struct timeval *);
+void ping4_install_filter( ping_handle_t *a_ping_handle, socket_st *);
+
+typedef struct ping_func_set_st {
+  int (*send_probe)(ping_handle_t *a_ping_handle, socket_st *, void *packet, unsigned packet_size);
+  int (*receive_error_msg)(ping_handle_t *a_ping_handle, socket_st *sock);
+  int (*parse_reply)(ping_handle_t *a_ping_handle, socket_st *, struct msghdr *msg, int len, void *addr, struct timeval *);
+  void (*install_filter)(ping_handle_t *a_ping_handle, socket_st *);
+} ping_func_set_st;
+
+extern ping_func_set_st ping4_func_set;
+
+extern int pinger(ping_handle_t *a_ping_handle, ping_func_set_st *fset, socket_st *sock);
+extern void sock_setbufs(ping_handle_t *a_ping_handle, socket_st*, int alloc);
+extern void setup(ping_handle_t *a_ping_handle, socket_st *);
+extern int contains_pattern_in_payload(ping_handle_t *a_ping_handle, uint8_t *ptr);
+extern void main_loop(ping_handle_t *a_ping_handle, ping_func_set_st *fset, socket_st*, uint8_t *buf, int buflen);// __attribute__((noreturn));
+extern void finish(ping_handle_t *a_ping_handle);// __attribute__((noreturn));
+extern void status(ping_handle_t *a_ping_handle);
+extern void common_options(int ch);
+extern int gather_statistics(ping_handle_t *a_ping_handle, uint8_t *ptr, int icmplen,
+           int cc, uint16_t seq, int hops,
+           int csfailed, struct timeval *tv, char *from,
+           void (*pr_reply)(uint8_t *ptr, int cc));
+extern void print_timestamp(ping_handle_t *a_ping_handle);
+void fill(ping_handle_t *a_ping_handle, char *patp, unsigned char *packet, unsigned packet_size);
+
+//extern int mark;
+//extern unsigned char outpack[MAXPACKET];
+
+/* IPv6 */
+
+int ping6_run(ping_handle_t *a_ping_handle, int argc, char **argv, struct addrinfo *ai, socket_st *sock);
+void ping6_usage(unsigned from_ping);
+
+int ping6_send_probe(ping_handle_t *a_ping_handle, socket_st *sockets, void *packet, unsigned packet_size);
+int ping6_receive_error_msg(ping_handle_t *a_ping_handle, socket_st *sock);
+int ping6_parse_reply(ping_handle_t *a_ping_handle, socket_st *, struct msghdr *msg, int len, void *addr, struct timeval *);
+void ping6_install_filter(ping_handle_t *a_ping_handle, socket_st *sockets);
+
+extern ping_func_set_st ping6_func_set;
+
+int niquery_option_handler(const char *opt_arg);
+
+extern uint32_t tclass;
+extern uint32_t flowlabel;
+extern struct sockaddr_in6 source6;
+extern struct sockaddr_in6 whereto6;
+extern struct sockaddr_in6 firsthop6;
+
+/* IPv6 node information query */
+
+#define NI_NONCE_SIZE     8
+
+struct ni_hdr {
+  struct icmp6_hdr    ni_u;
+  uint8_t       ni_nonce[NI_NONCE_SIZE];
+};
+
+#define ni_type   ni_u.icmp6_type
+#define ni_code   ni_u.icmp6_code
+#define ni_cksum  ni_u.icmp6_cksum
+#define ni_qtype  ni_u.icmp6_data16[0]
+#define ni_flags  ni_u.icmp6_data16[1]
+
+/* Types */
+#ifndef ICMPV6_NI_QUERY
+# define ICMPV6_NI_QUERY    139
+# define ICMPV6_NI_REPLY    140
+#endif
+
+/* Query Codes */
+#define NI_SUBJ_IPV6      0
+#define NI_SUBJ_NAME      1
+#define NI_SUBJ_IPV4      2
+
+/* Reply Codes */
+#define NI_SUCCESS      0
+#define NI_REFUSED      1
+#define NI_UNKNOWN      2
+
+/* Qtypes */
+#define NI_QTYPE_NOOP     0
+#define NI_QTYPE_NAME     2
+#define NI_QTYPE_IPV6ADDR   3
+#define NI_QTYPE_IPV4ADDR   4
+
+/* Flags */
+#define NI_IPV6ADDR_F_TRUNCATE    __constant_cpu_to_be16(0x0001)
+#define NI_IPV6ADDR_F_ALL   __constant_cpu_to_be16(0x0002)
+#define NI_IPV6ADDR_F_COMPAT    __constant_cpu_to_be16(0x0004)
+#define NI_IPV6ADDR_F_LINKLOCAL   __constant_cpu_to_be16(0x0008)
+#define NI_IPV6ADDR_F_SITELOCAL   __constant_cpu_to_be16(0x0010)
+#define NI_IPV6ADDR_F_GLOBAL    __constant_cpu_to_be16(0x0020)
+
+#define NI_IPV4ADDR_F_TRUNCATE    NI_IPV6ADDR_F_TRUNCATE
+#define NI_IPV4ADDR_F_ALL   NI_IPV6ADDR_F_ALL
+
+
diff --git a/libdap-chain-net/iputils/ping6_common.c b/libdap-chain-net/iputils/ping6_common.c
new file mode 100644
index 0000000000000000000000000000000000000000..3bb143fadd3c39f6bc58b146e380892f50e365aa
--- /dev/null
+++ b/libdap-chain-net/iputils/ping6_common.c
@@ -0,0 +1,1398 @@
+/*
+ *
+ *  Modified for AF_INET6 by Pedro Roque
+ *
+ *  <roque@di.fc.ul.pt>
+ *
+ *  Original copyright notice included bellow
+ */
+
+/*
+ * Copyright (c) 1989 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Mike Muuss.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+/*
+ *      P I N G . C
+ *
+ * Using the InterNet Control Message Protocol (ICMP) "ECHO" facility,
+ * measure round-trip-delays and packet loss across network paths.
+ *
+ * Author -
+ *  Mike Muuss
+ *  U. S. Army Ballistic Research Laboratory
+ *  December, 1983
+ *
+ * Status -
+ *  Public Domain.  Distribution Unlimited.
+ * Bugs -
+ *  More statistics could always be gathered.
+ *  If kernel does not support non-raw ICMP sockets or
+ *  if -N option is used, this program has to run SUID to ROOT or
+ *  with net_cap_raw enabled.
+ */
+
+#include "ping.h"
+
+/* IPv6 packet information.  */
+//struct in6_pktinfo
+//  {
+//    struct in6_addr ipi6_addr;  /* src/dst IPv6 address */
+//    unsigned int ipi6_ifindex;  /* send/recv interface index */
+//  };
+
+ping_func_set_st ping6_func_set = {
+    .send_probe = ping6_send_probe,
+    .receive_error_msg = ping6_receive_error_msg,
+    .parse_reply = ping6_parse_reply,
+    .install_filter = ping6_install_filter
+};
+
+#ifndef SCOPE_DELIMITER
+# define SCOPE_DELIMITER '%'
+#endif
+
+uint32_t flowlabel;
+uint32_t tclass;
+
+static struct sockaddr_in6 whereto;
+static struct sockaddr_in6 firsthop;
+
+static unsigned char cmsgbuf[4096];
+static size_t cmsglen = 0;
+
+static int pr_icmph(uint8_t type, uint8_t code, uint32_t info);
+
+struct sockaddr_in6 source6 = { .sin6_family = AF_INET6 };
+//extern char *device;
+
+#if defined(USE_GCRYPT) || defined(USE_OPENSSL) || defined(USE_NETTLE)
+#include "iputils_md5dig.h"
+#define USE_CRYPTO
+#endif
+
+/* Node Information query */
+int ni_query = -1;
+int ni_flag = 0;
+void *ni_subject = NULL;
+int ni_subject_len = 0;
+int ni_subject_type = -1;
+char *ni_group;
+
+static inline int ntohsp(uint16_t *p)
+{
+    uint16_t v;
+    memcpy(&v, p, sizeof(v));
+    return ntohs(v);
+}
+
+unsigned int if_name2index(const char *ifname)
+{
+    unsigned int i = if_nametoindex(ifname);
+    if(!i)
+        error(2, 0, "unknown iface: %s", ifname);
+    return i;
+}
+
+struct niquery_option {
+    char *name;
+    int namelen;
+    int has_arg;
+    int data;
+    int (*handler)(int index, const char *arg);
+};
+
+#define NIQUERY_OPTION(_name, _has_arg, _data, _handler)  \
+  {             \
+    .name = _name,          \
+    .namelen = sizeof(_name) - 1,     \
+    .has_arg = _has_arg,        \
+    .data = _data,          \
+    .handler = _handler       \
+  }
+
+static int niquery_option_name_handler(int index __attribute__((__unused__)),
+        const char *arg __attribute__((__unused__)));
+static int niquery_option_ipv6_handler(int index __attribute__((__unused__)),
+        const char *arg __attribute__((__unused__)));
+static int niquery_option_ipv6_flag_handler(int index, const char *arg);
+static int niquery_option_ipv4_handler(int index, const char *arg);
+static int niquery_option_ipv4_flag_handler(int index, const char *arg);
+static int niquery_option_subject_addr_handler(int index, const char *arg);
+static int niquery_option_subject_name_handler(int index, const char *arg);
+static int niquery_option_help_handler(int index, const char *arg);
+
+struct niquery_option niquery_options[] = {
+NIQUERY_OPTION("name", 0, 0, niquery_option_name_handler),
+NIQUERY_OPTION("fqdn", 0, 0, niquery_option_name_handler),
+NIQUERY_OPTION("ipv6", 0, 0, niquery_option_ipv6_handler),
+NIQUERY_OPTION("ipv6-all", 0, NI_IPV6ADDR_F_ALL, niquery_option_ipv6_flag_handler),
+NIQUERY_OPTION("ipv6-compatible", 0, NI_IPV6ADDR_F_COMPAT, niquery_option_ipv6_flag_handler),
+NIQUERY_OPTION("ipv6-linklocal", 0, NI_IPV6ADDR_F_LINKLOCAL, niquery_option_ipv6_flag_handler),
+NIQUERY_OPTION("ipv6-sitelocal", 0, NI_IPV6ADDR_F_SITELOCAL, niquery_option_ipv6_flag_handler),
+NIQUERY_OPTION("ipv6-global", 0, NI_IPV6ADDR_F_GLOBAL, niquery_option_ipv6_flag_handler),
+NIQUERY_OPTION("ipv4", 0, 0, niquery_option_ipv4_handler),
+NIQUERY_OPTION("ipv4-all", 0, NI_IPV4ADDR_F_ALL, niquery_option_ipv4_flag_handler),
+NIQUERY_OPTION("subject-ipv6", 1, NI_SUBJ_IPV6, niquery_option_subject_addr_handler),
+NIQUERY_OPTION("subject-ipv4", 1, NI_SUBJ_IPV4, niquery_option_subject_addr_handler),
+NIQUERY_OPTION("subject-name", 1, 0, niquery_option_subject_name_handler),
+NIQUERY_OPTION("subject-fqdn", 1, -1, niquery_option_subject_name_handler),
+NIQUERY_OPTION("help", 0, 0, niquery_option_help_handler),
+    { NULL, 0, 0, 0, NULL }
+};
+
+static inline int niquery_is_enabled(void)
+{
+    return ni_query >= 0;
+}
+
+#if PING6_NONCE_MEMORY
+uint8_t *ni_nonce_ptr;
+#else
+struct {
+    struct timeval tv;
+    pid_t pid;
+} ni_nonce_secret;
+#endif
+
+static void niquery_init_nonce(void)
+{
+#if PING6_NONCE_MEMORY
+    struct timeval tv;
+    unsigned long seed;
+
+    seed = (unsigned long)getpid();
+    if (!gettimeofday(&tv, NULL))
+    seed ^= tv.tv_usec;
+    srand(seed);
+
+    ni_nonce_ptr = calloc(NI_NONCE_SIZE, MAX_DUP_CHK);
+    if (!ni_nonce_ptr)
+    error(2, errno, "calloc");
+
+    ni_nonce_ptr[0] = ~0;
+#else
+    gettimeofday(&ni_nonce_secret.tv, NULL);
+    ni_nonce_secret.pid = getpid();
+#endif
+}
+
+#if !PING6_NONCE_MEMORY
+static int niquery_nonce(uint8_t *nonce, int fill)
+{
+# ifdef USE_CRYPTO
+    static uint8_t digest[MD5_DIGEST_LENGTH];
+    static int seq = -1;
+
+    if (fill || seq != *(uint16_t *)nonce || seq < 0) {
+        MD5_CTX ctxt;
+
+        MD5_Init(&ctxt);
+        MD5_Update(&ctxt, &ni_nonce_secret, sizeof(ni_nonce_secret));
+        MD5_Update(&ctxt, nonce, sizeof(uint16_t));
+        MD5_Final(digest, &ctxt);
+
+        seq = *(uint16_t *)nonce;
+    }
+
+    if (fill) {
+        memcpy(nonce + sizeof(uint16_t), digest, NI_NONCE_SIZE - sizeof(uint16_t));
+        return 0;
+    } else {
+        if (memcmp(nonce + sizeof(uint16_t), digest, NI_NONCE_SIZE - sizeof(uint16_t)))
+        return -1;
+        return ntohsp((uint16_t *)nonce);
+    }
+# else
+    error(3, ENOSYS, "niquery_nonce() crypto disabled");
+# endif
+    if(nonce || fill)
+        return -1;
+    return -1;
+}
+#endif
+
+static inline void niquery_fill_nonce(uint16_t seq, uint8_t *nonce)
+{
+    uint16_t v = htons(seq);
+#if PING6_NONCE_MEMORY
+    int i;
+
+    memcpy(&ni_nonce_ptr[NI_NONCE_SIZE * (seq % MAX_DUP_CHK)], &v, sizeof(v));
+
+    for (i = sizeof(v); i < NI_NONCE_SIZE; i++)
+    ni_nonce_ptr[NI_NONCE_SIZE * (seq % MAX_DUP_CHK) + i] = 0x100 * (rand() / (RAND_MAX + 1.0));
+
+    memcpy(nonce, &ni_nonce_ptr[NI_NONCE_SIZE * (seq % MAX_DUP_CHK)], NI_NONCE_SIZE);
+#else
+    memcpy(nonce, &v, sizeof(v));
+    niquery_nonce(nonce, 1);
+#endif
+}
+
+static inline int niquery_check_nonce(uint8_t *nonce)
+{
+#if PING6_NONCE_MEMORY
+    uint16_t seq = ntohsp((uint16_t *)nonce);
+    if (memcmp(nonce, &ni_nonce_ptr[NI_NONCE_SIZE * (seq % MAX_DUP_CHK)], NI_NONCE_SIZE))
+    return -1;
+    return seq;
+#else
+    return niquery_nonce(nonce, 0);
+#endif
+}
+
+static int niquery_set_qtype(int type)
+{
+    if(niquery_is_enabled() && ni_query != type) {
+        printf("Qtype conflict\n");
+        return -1;
+    }
+    ni_query = type;
+    return 0;
+}
+
+static int niquery_option_name_handler(int index __attribute__((__unused__)),
+        const char *arg __attribute__((__unused__)))
+{
+    if(niquery_set_qtype(NI_QTYPE_NAME) < 0)
+        return -1;
+    return 0;
+}
+
+static int niquery_option_ipv6_handler(int index __attribute__((__unused__)),
+        const char *arg __attribute__((__unused__)))
+{
+    if(niquery_set_qtype(NI_QTYPE_IPV6ADDR) < 0)
+        return -1;
+    return 0;
+}
+
+static int niquery_option_ipv6_flag_handler(int index, const char *arg __attribute__((__unused__)))
+{
+    if(niquery_set_qtype(NI_QTYPE_IPV6ADDR) < 0)
+        return -1;
+    ni_flag |= niquery_options[index].data;
+    return 0;
+}
+
+static int niquery_option_ipv4_handler(int index __attribute__((__unused__)),
+        const char *arg __attribute__((__unused__)))
+{
+    if(niquery_set_qtype(NI_QTYPE_IPV4ADDR) < 0)
+        return -1;
+    return 0;
+}
+
+static int niquery_option_ipv4_flag_handler(int index, const char *arg __attribute__((__unused__)))
+{
+    if(niquery_set_qtype(NI_QTYPE_IPV4ADDR) < 0)
+        return -1;
+    ni_flag |= niquery_options[index].data;
+    return 0;
+}
+
+static inline int niquery_is_subject_valid(void)
+{
+    return ni_subject_type >= 0 && ni_subject;
+}
+
+static int niquery_set_subject_type(int type)
+{
+    if(niquery_is_subject_valid() && ni_subject_type != type) {
+        printf("Subject type conflict\n");
+        return -1;
+    }
+    ni_subject_type = type;
+    return 0;
+}
+
+#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))
+#define OFFSET_OF(type,elem)  ((size_t)&((type *)0)->elem)
+
+static int niquery_option_subject_addr_handler(int index, const char *arg)
+{
+    struct addrinfo hints = { .ai_family = AF_UNSPEC, .ai_socktype = SOCK_DGRAM, .ai_flags = getaddrinfo_flags };
+    struct addrinfo *result, *ai;
+    int status;
+    int offset;
+
+    if(niquery_set_subject_type(niquery_options[index].data) < 0)
+        return -1;
+
+    ni_subject_type = niquery_options[index].data;
+
+    switch (niquery_options[index].data) {
+    case NI_SUBJ_IPV6:
+        ni_subject_len = sizeof(struct in6_addr);
+        offset = OFFSET_OF(struct sockaddr_in6, sin6_addr);
+        hints.ai_family = AF_INET6;
+        break;
+    case NI_SUBJ_IPV4:
+        ni_subject_len = sizeof(struct in_addr);
+        offset = OFFSET_OF(struct sockaddr_in, sin_addr);
+        hints.ai_family = AF_INET;
+        break;
+    default:
+        /* should not happen. */
+        offset = -1;
+    }
+
+    status = getaddrinfo(arg, 0, &hints, &result);
+    if(status) {
+        error(0, 0, "%s: %s", arg, gai_strerror(status));
+        return -1;
+    }
+
+    for(ai = result; ai; ai = ai->ai_next) {
+        void *p = malloc(ni_subject_len);
+        if(!p)
+            continue;
+        memcpy(p, (uint8_t *) ai->ai_addr + offset, ni_subject_len);
+        free(ni_subject);
+        ni_subject = p;
+        break;
+    }
+    freeaddrinfo(result);
+
+    return 0;
+}
+
+#ifdef USE_IDN
+# if IDN2_VERSION_NUMBER >= 0x02000000
+#  define IDN2_FLAGS IDN2_NONTRANSITIONAL
+# else
+#  define IDN2_FLAGS 0
+# endif
+#endif
+
+#ifdef USE_CRYPTO
+static int niquery_option_subject_name_handler(int index, const char *name)
+{
+    static char nigroup_buf[INET6_ADDRSTRLEN + 1 + IFNAMSIZ];
+    unsigned char *dnptrs[2], **dpp, **lastdnptr;
+    int n;
+    size_t i;
+    char *p;
+    char *canonname = NULL, *idn = NULL;
+    unsigned char *buf = NULL;
+    size_t namelen;
+    size_t buflen;
+    int dots, fqdn = niquery_options[index].data;
+    MD5_CTX ctxt;
+    uint8_t digest[MD5_DIGEST_LENGTH];
+#ifdef USE_IDN
+    int rc;
+#endif
+
+    if (niquery_set_subject_type(NI_SUBJ_NAME) < 0)
+    return -1;
+
+#ifdef USE_IDN
+    rc = idn2_lookup_ul(name, &idn, IDN2_FLAGS);
+    if (rc)
+    error(2, 0, "IDN encoding error: %s", idn2_strerror(rc));
+#else
+    idn = strdup(name);
+    if (!idn)
+    goto oomexit;
+#endif
+
+    p = strchr(idn, SCOPE_DELIMITER);
+    if (p) {
+        *p = '\0';
+        if (strlen(p + 1) >= IFNAMSIZ)
+        error(1, 0, "too long scope name");
+    }
+
+    namelen = strlen(idn);
+    canonname = malloc(namelen + 1);
+    if (!canonname)
+    goto oomexit;
+
+    dots = 0;
+    for (i = 0; i < namelen + 1; i++) {
+        canonname[i] = isupper(idn[i]) ? tolower(idn[i]) : idn[i];
+        if (idn[i] == '.')
+        dots++;
+    }
+
+    if (fqdn == 0) {
+        /* guess if hostname is FQDN */
+        fqdn = dots ? 1 : -1;
+    }
+
+    buflen = namelen + 3 + 1; /* dn_comp() requrires strlen() + 3,
+     plus non-fqdn indicator. */
+    buf = malloc(buflen);
+    if (!buf) {
+        error(0, errno, "memory allocation failed");
+        goto errexit;
+    }
+
+    dpp = dnptrs;
+    lastdnptr = &dnptrs[ARRAY_SIZE(dnptrs)];
+
+    *dpp++ = (unsigned char *)buf;
+    *dpp++ = NULL;
+
+    n = dn_comp(canonname, (unsigned char *)buf, buflen, dnptrs, lastdnptr);
+    if (n < 0) {
+        error(0, 0, "inappropriate subject name: %s", canonname);
+        goto errexit;
+    } else if ((size_t) n >= buflen) {
+        error(0, 0, "dn_comp() returned too long result");
+        goto errexit;
+    }
+
+    MD5_Init(&ctxt);
+    MD5_Update(&ctxt, buf, buf[0]);
+    MD5_Final(digest, &ctxt);
+
+    sprintf(nigroup_buf, "ff02::2:%02x%02x:%02x%02x%s%s",
+            digest[0], digest[1], digest[2], digest[3],
+            p ? "%" : "",
+            p ? p + 1 : "");
+
+    if (fqdn < 0)
+    buf[n] = 0;
+
+    free(ni_subject);
+
+    ni_group = nigroup_buf;
+    ni_subject = buf;
+    ni_subject_len = n + (fqdn < 0);
+    ni_group = nigroup_buf;
+
+    free(canonname);
+    free(idn);
+
+    return 0;
+    oomexit:
+    error(0, errno, "memory allocation failed");
+    errexit:
+    free(buf);
+    free(canonname);
+    free(idn);
+    exit(1);
+}
+#else
+static int niquery_option_subject_name_handler(int index __attribute__((__unused__)),
+        const char *name __attribute__((__unused__)))
+{
+    error(3, ENOSYS, "niquery_option_subject_name_handler() crypto disabled");
+    return -1;
+}
+#endif
+
+int niquery_option_help_handler(int index __attribute__((__unused__)), const char *arg __attribute__((__unused__)))
+{
+    fprintf(stderr, "ping -6 -N <nodeinfo opt>\n"
+            "Help:\n"
+            "  help\n"
+            "Query:\n"
+            "  name\n"
+            "  ipv6\n"
+            "  ipv6-all\n"
+            "  ipv6-compatible\n"
+            "  ipv6-global\n"
+            "  ipv6-linklocal\n"
+            "  ipv6-sitelocal\n"
+            "  ipv4\n"
+            "  ipv4-all\n"
+            "Subject:\n"
+            "  subject-ipv6=addr\n"
+            "  subject-ipv4=addr\n"
+            "  subject-name=name\n"
+            "  subject-fqdn=name\n"
+            );
+    exit(2);
+}
+
+int niquery_option_handler(const char *opt_arg)
+{
+    struct niquery_option *p;
+    int i;
+    int ret = -1;
+    for(i = 0, p = niquery_options; p->name; i++, p++) {
+        if(strncmp(p->name, opt_arg, p->namelen))
+            continue;
+        if(!p->has_arg) {
+            if(opt_arg[p->namelen] == '\0') {
+                ret = p->handler(i, NULL);
+                if(ret >= 0)
+                    break;
+            }
+        } else {
+            if(opt_arg[p->namelen] == '=') {
+                ret = p->handler(i, &opt_arg[p->namelen] + 1);
+                if(ret >= 0)
+                    break;
+            }
+        }
+    }
+    if(!p->name)
+        ret = niquery_option_help_handler(0, NULL);
+    return ret;
+}
+
+int ping6_run(ping_handle_t *a_ping_handle, int argc, char **argv, struct addrinfo *ai, struct socket_st *sock)
+{
+    static const struct addrinfo hints = { .ai_family = AF_INET6, .ai_flags = getaddrinfo_flags };
+    struct addrinfo *result = NULL;
+    int status;
+    int hold, packlen;
+    unsigned char *packet;
+    char *target;
+    struct icmp6_filter filter;
+    int err;
+    static uint32_t scope_id = 0;
+
+    if(niquery_is_enabled()) {
+        niquery_init_nonce();
+
+        if(!niquery_is_subject_valid()) {
+            ni_subject = &whereto.sin6_addr;
+            ni_subject_len = sizeof(whereto.sin6_addr);
+            ni_subject_type = NI_SUBJ_IPV6;
+        }
+    }
+
+    if(argc > 1) {
+        usage();
+    } else if(argc == 1) {
+        target = *argv;
+    } else {
+        if(ni_query < 0 && ni_subject_type != NI_SUBJ_NAME)
+            usage();
+        target = ni_group;
+    }
+
+    if(!ai) {
+        status = getaddrinfo(target, NULL, &hints, &result);
+        if(status)
+            error(2, 0, "%s: %s", target, gai_strerror(status));
+        ai = result;
+    }
+
+    memcpy(&whereto, ai->ai_addr, sizeof(whereto));
+    whereto.sin6_port = htons(IPPROTO_ICMPV6);
+
+    if(result)
+        freeaddrinfo(result);
+
+    if(memchr(target, ':', strlen(target)))
+        a_ping_handle->ping_common.options |= F_NUMERIC;
+
+    if(IN6_IS_ADDR_UNSPECIFIED(&firsthop.sin6_addr)) {
+        memcpy(&firsthop.sin6_addr, &whereto.sin6_addr, 16);
+        firsthop.sin6_scope_id = whereto.sin6_scope_id;
+        /* Verify scope_id is the same as intermediate nodes */
+        if(firsthop.sin6_scope_id && scope_id && firsthop.sin6_scope_id != scope_id)
+            error(2, 0, "scope discrepancy among the nodes");
+        else if(!scope_id)
+            scope_id = firsthop.sin6_scope_id;
+    }
+
+    a_ping_handle->ping_common.hostname = target;
+
+    if(IN6_IS_ADDR_UNSPECIFIED(&source6.sin6_addr)) {
+        socklen_t alen;
+        int probe_fd = socket(AF_INET6, SOCK_DGRAM, 0);
+
+        if(probe_fd < 0)
+            error(2, errno, "socket");
+        if(a_ping_handle->device) {
+            unsigned int iface = if_name2index(a_ping_handle->device);
+#ifdef IPV6_RECVPKTINFO
+            struct in6_pktinfo ipi;
+
+            memset(&ipi, 0, sizeof(ipi));
+            ipi.ipi6_ifindex = iface;
+#endif
+
+            if(IN6_IS_ADDR_LINKLOCAL(&firsthop.sin6_addr) ||
+                    IN6_IS_ADDR_MC_LINKLOCAL(&firsthop.sin6_addr))
+                firsthop.sin6_scope_id = iface;
+            enable_capability_raw(a_ping_handle);
+#ifdef IPV6_RECVPKTINFO
+            if(
+            setsockopt(probe_fd, IPPROTO_IPV6, IPV6_PKTINFO, &ipi, sizeof ipi) == -1 ||
+                    setsockopt(sock->fd, IPPROTO_IPV6, IPV6_PKTINFO, &ipi, sizeof ipi) == -1) {
+                perror("setsockopt(IPV6_PKTINFO)");
+                exit(2);
+            }
+#endif
+            if(
+            setsockopt(probe_fd, SOL_SOCKET, SO_BINDTODEVICE, a_ping_handle->device, strlen(a_ping_handle->device) + 1) == -1 ||
+                    setsockopt(sock->fd, SOL_SOCKET, SO_BINDTODEVICE, a_ping_handle->device, strlen(a_ping_handle->device) + 1) == -1) {
+                error(2, errno, "setsockopt(SO_BINDTODEVICE) %s", a_ping_handle->device);
+            }
+            disable_capability_raw(a_ping_handle);
+        }
+
+        if(!IN6_IS_ADDR_LINKLOCAL(&firsthop.sin6_addr) &&
+                !IN6_IS_ADDR_MC_LINKLOCAL(&firsthop.sin6_addr))
+            firsthop.sin6_family = AF_INET6;
+
+        firsthop.sin6_port = htons(1025);
+        if(connect(probe_fd, (struct sockaddr*) &firsthop, sizeof(firsthop)) == -1)
+            error(2, errno, "connect");
+        alen = sizeof source6;
+        if(getsockname(probe_fd, (struct sockaddr *) &source6, &alen) == -1)
+            error(2, errno, "getsockname");
+        source6.sin6_port = 0;
+        close(probe_fd);
+
+        if(a_ping_handle->device) {
+            struct ifaddrs *ifa0, *ifa;
+
+            if(getifaddrs(&ifa0))
+                error(2, errno, "getifaddrs");
+
+            for(ifa = ifa0; ifa; ifa = ifa->ifa_next) {
+                if(!ifa->ifa_name || !ifa->ifa_addr ||
+                        ifa->ifa_addr->sa_family != AF_INET6)
+                    continue;
+                if(!strcmp(ifa->ifa_name, a_ping_handle->device) &&
+                        IN6_ARE_ADDR_EQUAL(&((struct sockaddr_in6 * )ifa->ifa_addr)->sin6_addr,
+                                &source6.sin6_addr))
+                    break;
+            }
+            if(!ifa)
+                error(0, 0, "Warning: source address might be selected on device other than: %s", a_ping_handle->device);
+
+            freeifaddrs(ifa0);
+        }
+    }
+    else if(a_ping_handle->device && (IN6_IS_ADDR_LINKLOCAL(&source6.sin6_addr) ||
+            IN6_IS_ADDR_MC_LINKLOCAL(&source6.sin6_addr)))
+        source6.sin6_scope_id = if_name2index(a_ping_handle->device);
+
+    if(a_ping_handle->device) {
+        struct cmsghdr *cmsg;
+        struct in6_pktinfo *ipi;
+
+        cmsg = (struct cmsghdr*) (cmsgbuf + cmsglen);
+        cmsglen += CMSG_SPACE(sizeof(*ipi));
+        cmsg->cmsg_len = CMSG_LEN(sizeof(*ipi));
+        cmsg->cmsg_level = IPPROTO_IPV6;
+        cmsg->cmsg_type = IPV6_PKTINFO;
+
+        ipi = (struct in6_pktinfo*) CMSG_DATA(cmsg);
+        memset(ipi, 0, sizeof(*ipi));
+        ipi->ipi6_ifindex = if_name2index(a_ping_handle->device);
+    }
+
+    if((whereto.sin6_addr.s6_addr16[0] & htons(0xff00)) == htons(0xff00)) {
+        if(a_ping_handle->ping_common.uid) {
+            if(a_ping_handle->ping_common.interval < 1000)
+                error(2, 0, "multicast ping with too short interval: %d", a_ping_handle->ping_common.interval);
+            if(a_ping_handle->pmtudisc >= 0 && a_ping_handle->pmtudisc != IPV6_PMTUDISC_DO)
+                error(2, 0, "multicast ping does not fragment");
+        }
+        if(a_ping_handle->pmtudisc < 0)
+            a_ping_handle->pmtudisc = IPV6_PMTUDISC_DO;
+    }
+
+    if(a_ping_handle->pmtudisc >= 0) {
+        if(setsockopt(sock->fd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &a_ping_handle->pmtudisc, sizeof (a_ping_handle->pmtudisc)) == -1)
+            error(2, errno, "IPV6_MTU_DISCOVER");
+    }
+
+    if((a_ping_handle->ping_common.options & F_STRICTSOURCE) &&
+            bind(sock->fd, (struct sockaddr *) &source6, sizeof source6) == -1)
+        error(2, errno, "bind icmp socket");
+
+    if((ssize_t) a_ping_handle->ping_common.datalen >= (ssize_t) sizeof(struct timeval) && (ni_query < 0)) {
+        /* can we time transfer */
+        a_ping_handle->ping_common.timing = 1;
+    }
+    packlen = a_ping_handle->ping_common.datalen + 8 + 4096 + 40 + 8; /* 4096 for rthdr */
+    if(!(packet = (unsigned char *) malloc((unsigned int) packlen)))
+        error(2, errno, "memory allocation failed");
+
+    hold = 1;
+
+    /* Estimate memory eaten by single packet. It is rough estimate.
+     * Actually, for small datalen's it depends on kernel side a lot. */
+    hold = a_ping_handle->ping_common.datalen + 8;
+    hold += ((hold + 511) / 512) * (40 + 16 + 64 + 160);
+    sock_setbufs(a_ping_handle,sock, hold);
+
+#ifdef __linux__
+    if(sock->socktype == SOCK_RAW) {
+        int csum_offset = 2;
+        int sz_opt = sizeof(int);
+
+        err = setsockopt(sock->fd, SOL_RAW, IPV6_CHECKSUM, &csum_offset, sz_opt);
+        if(err < 0) {
+            /* checksum should be enabled by default and setting this
+             * option might fail anyway.
+             */
+            error(0, errno, "setsockopt(RAW_CHECKSUM) failed - try to continue");
+        }
+#else
+        {
+#endif
+
+        /*
+         *  select icmp echo reply as icmp type to receive
+         */
+
+        ICMP6_FILTER_SETBLOCKALL(&filter);
+
+        ICMP6_FILTER_SETPASS(ICMP6_DST_UNREACH, &filter);
+        ICMP6_FILTER_SETPASS(ICMP6_PACKET_TOO_BIG, &filter);
+        ICMP6_FILTER_SETPASS(ICMP6_TIME_EXCEEDED, &filter);
+        ICMP6_FILTER_SETPASS(ICMP6_PARAM_PROB, &filter);
+
+        if(niquery_is_enabled())
+            ICMP6_FILTER_SETPASS(ICMPV6_NI_REPLY, &filter);
+        else
+            ICMP6_FILTER_SETPASS(ICMP6_ECHO_REPLY, &filter);
+
+        err = setsockopt(sock->fd, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof filter);
+
+        if(err < 0)
+            error(2, errno, "setsockopt(ICMP6_FILTER)");
+    }
+
+    if(a_ping_handle->ping_common.options & F_NOLOOP) {
+        int loop = 0;
+        if(setsockopt(sock->fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &loop, sizeof loop) == -1)
+            error(2, errno, "can't disable multicast loopback");
+    }
+    if(a_ping_handle->ping_common.options & F_TTL) {
+        if(setsockopt(sock->fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &a_ping_handle->ping_common.ttl, sizeof (a_ping_handle->ping_common.ttl)) == -1)
+            error(2, errno, "can't set multicast hop limit");
+        if(setsockopt(sock->fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &a_ping_handle->ping_common.ttl, sizeof (a_ping_handle->ping_common.ttl)) == -1)
+            error(2, errno, "can't set unicast hop limit");
+    }
+
+    const int on = 1;
+    if(
+    #ifdef IPV6_RECVHOPLIMIT
+    setsockopt(sock->fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &on, sizeof on) == -1 &&
+            setsockopt(sock->fd, IPPROTO_IPV6, IPV6_2292HOPLIMIT, &on, sizeof on) == -1
+                    #else
+                    setsockopt(sock->fd, IPPROTO_IPV6, IPV6_HOPLIMIT, &on, sizeof on) == -1
+#endif
+                    )
+        error(2, errno, "can't receive hop limit");
+
+    if(a_ping_handle->ping_common.options & F_TCLASS) {
+#ifdef IPV6_TCLASS
+        if(setsockopt(sock->fd, IPPROTO_IPV6, IPV6_TCLASS, &tclass, sizeof tclass) == -1)
+            error(2, errno, "setsockopt(IPV6_TCLASS)");
+#else
+        error(0, 0, "traffic class is not supported");
+#endif
+    }
+
+    if(a_ping_handle->ping_common.options & F_FLOWINFO) {
+#ifdef IPV6_FLOWLABEL_MGR
+        char freq_buf[CMSG_ALIGN(sizeof(struct in6_flowlabel_req)) + cmsglen];
+        struct in6_flowlabel_req *freq = (struct in6_flowlabel_req *)freq_buf;
+        int freq_len = sizeof(*freq);
+        memset(freq, 0, sizeof(*freq));
+        freq->flr_label = htonl(flowlabel & IPV6_FLOWINFO_FLOWLABEL);
+        freq->flr_action = IPV6_FL_A_GET;
+        freq->flr_flags = IPV6_FL_F_CREATE;
+        freq->flr_share = IPV6_FL_S_EXCL;
+        memcpy(&freq->flr_dst, &whereto.sin6_addr, 16);
+        if (setsockopt(sock->fd, IPPROTO_IPV6, IPV6_FLOWLABEL_MGR, freq, freq_len) == -1)
+        error(2, errno, "can't set flowlabel");
+        flowlabel = freq->flr_label;
+#else
+        error(2, 0, "flow labels are not supported");
+#endif
+
+#ifdef IPV6_FLOWINFO_SEND
+        whereto.sin6_flowinfo = flowlabel;
+        if (setsockopt(sock->fd, IPPROTO_IPV6, IPV6_FLOWINFO_SEND, &on, sizeof on) == -1)
+        error(2, errno, "can't send flowinfo");
+#else
+        error(2, 0, "flowinfo is not supported");
+#endif
+    }
+
+    printf("PING %s(%s) ", a_ping_handle->ping_common.hostname, pr_addr(a_ping_handle, &whereto, sizeof whereto));
+    if(flowlabel)
+        printf(", flow 0x%05x, ", (unsigned) ntohl(flowlabel));
+    if(a_ping_handle->device || (a_ping_handle->ping_common.options & F_STRICTSOURCE)) {
+        int saved_options = a_ping_handle->ping_common.options;
+
+        a_ping_handle->ping_common.options |= F_NUMERIC;
+        printf("from %s %s: ", pr_addr(a_ping_handle, &source6, sizeof source6), a_ping_handle->device ? a_ping_handle->device : "");
+        a_ping_handle->ping_common.options = saved_options;
+    }
+    printf("%d data bytes\n", a_ping_handle->ping_common.datalen);
+
+    setup(a_ping_handle, sock);
+
+    drop_capabilities();
+
+    main_loop(a_ping_handle, &ping6_func_set, sock, packet, packlen);
+    return 0;
+}
+
+int ping6_receive_error_msg(ping_handle_t *a_ping_handle, socket_st *sock)
+{
+    ssize_t res;
+    char cbuf[512];
+    struct iovec iov;
+    struct msghdr msg;
+    struct cmsghdr *cmsg;
+    struct sock_extended_err *e;
+    struct icmp6_hdr icmph;
+    struct sockaddr_in6 target;
+    int net_errors = 0;
+    int local_errors = 0;
+    int saved_errno = errno;
+
+    iov.iov_base = &icmph;
+    iov.iov_len = sizeof(icmph);
+    msg.msg_name = (void*) &target;
+    msg.msg_namelen = sizeof(target);
+    msg.msg_iov = &iov;
+    msg.msg_iovlen = 1;
+    msg.msg_flags = 0;
+    msg.msg_control = cbuf;
+    msg.msg_controllen = sizeof(cbuf);
+
+    res = recvmsg(sock->fd, &msg, MSG_ERRQUEUE | MSG_DONTWAIT);
+    if(res < 0)
+        goto out;
+
+    e = NULL;
+    for(cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+        if(cmsg->cmsg_level == IPPROTO_IPV6) {
+            if(cmsg->cmsg_type == IPV6_RECVERR)
+                e = (struct sock_extended_err *) CMSG_DATA(cmsg);
+        }
+    }
+    if(e == NULL)
+        abort();
+
+    if(e->ee_origin == SO_EE_ORIGIN_LOCAL) {
+        local_errors++;
+        if(a_ping_handle->ping_common.options & F_QUIET)
+            goto out;
+        if(a_ping_handle->ping_common.options & F_FLOOD)
+            write_stdout("E", 1);
+        else if(e->ee_errno != EMSGSIZE)
+            error(0, e->ee_errno, "local error");
+        else
+            error(0, 0, "local error: message too long, mtu: %u", e->ee_info);
+        a_ping_handle->ping_common.nerrors++;
+    } else if(e->ee_origin == SO_EE_ORIGIN_ICMP6) {
+        struct sockaddr_in6 *sin6 = (struct sockaddr_in6*) (e + 1);
+
+        if((size_t) res < sizeof(icmph) ||
+                memcmp(&target.sin6_addr, &whereto.sin6_addr, 16) ||
+                icmph.icmp6_type != ICMP6_ECHO_REQUEST ||
+                !is_ours(a_ping_handle, sock, icmph.icmp6_id)) {
+            /* Not our error, not an error at all. Clear. */
+            saved_errno = 0;
+            goto out;
+        }
+
+        net_errors++;
+        a_ping_handle->ping_common.nerrors++;
+        if(a_ping_handle->ping_common.options & F_QUIET)
+            goto out;
+        if(a_ping_handle->ping_common.options & F_FLOOD) {
+            write_stdout("\bE", 2);
+        } else {
+            print_timestamp(a_ping_handle);
+            printf("From %s icmp_seq=%u ", pr_addr(a_ping_handle, sin6, sizeof *sin6), ntohs(icmph.icmp6_seq));
+            pr_icmph(e->ee_type, e->ee_code, e->ee_info);
+            putchar('\n');
+            fflush(stdout);
+        }
+    }
+
+    out:
+    errno = saved_errno;
+    return net_errors ? net_errors : -local_errors;
+}
+
+/*
+ * pinger --
+ *  Compose and transmit an ICMP ECHO REQUEST packet.  The IP packet
+ * will be added on by the kernel.  The ID field is our UNIX process ID,
+ * and the sequence number is an ascending integer.  The first several bytes
+ * of the data portion are used to hold a UNIX "timeval" struct in VAX
+ * byte-order, to compute the round-trip time.
+ */
+static int build_echo(ping_handle_t *a_ping_handle, uint8_t *_icmph, unsigned packet_size __attribute__((__unused__)))
+{
+    struct icmp6_hdr *icmph;
+    int cc;
+
+    icmph = (struct icmp6_hdr *) _icmph;
+    icmph->icmp6_type = ICMP6_ECHO_REQUEST;
+    icmph->icmp6_code = 0;
+    icmph->icmp6_cksum = 0;
+    icmph->icmp6_seq= htons(a_ping_handle->ping_common.ntransmitted+1);
+    icmph->icmp6_id= a_ping_handle->ping_common.ident;
+
+    if(a_ping_handle->ping_common.timing)
+        gettimeofday((struct timeval *) &_icmph[8],
+                (struct timezone *) NULL);
+
+    cc = a_ping_handle->ping_common.datalen + 8; /* skips ICMP portion */
+
+    return cc;
+}
+
+static int build_niquery(ping_handle_t *a_ping_handle, uint8_t *_nih, unsigned packet_size __attribute__((__unused__)))
+{
+    struct ni_hdr *nih;
+    int cc;
+
+    nih = (struct ni_hdr *) _nih;
+    nih->ni_cksum = 0;
+
+    nih->ni_type = ICMPV6_NI_QUERY;
+    cc = sizeof(*nih);
+    a_ping_handle->ping_common.datalen = 0;
+
+    niquery_fill_nonce(a_ping_handle->ping_common.ntransmitted + 1, nih->ni_nonce);
+    nih->ni_code = ni_subject_type;
+    nih->ni_qtype= htons(ni_query);
+    nih->ni_flags= ni_flag;
+    memcpy(nih + 1, ni_subject, ni_subject_len);
+    cc += ni_subject_len;
+
+    return cc;
+}
+
+int ping6_send_probe(ping_handle_t *a_ping_handle, socket_st *sock, void *packet, unsigned packet_size)
+{
+    int len, cc;
+
+    rcvd_clear(a_ping_handle, a_ping_handle->ping_common.ntransmitted + 1);
+
+    if(niquery_is_enabled())
+        len = build_niquery(a_ping_handle, packet, packet_size);
+    else
+        len = build_echo(a_ping_handle, packet, packet_size);
+
+    if(cmsglen == 0) {
+        cc = sendto(sock->fd, (char *) packet, len, a_ping_handle->ping_common.confirm,
+                (struct sockaddr *) &whereto,
+                sizeof(struct sockaddr_in6));
+    } else {
+        struct msghdr mhdr;
+        struct iovec iov;
+
+        iov.iov_len = len;
+        iov.iov_base = packet;
+
+        memset(&mhdr, 0, sizeof(mhdr));
+        mhdr.msg_name = &whereto;
+        mhdr.msg_namelen = sizeof(struct sockaddr_in6);
+        mhdr.msg_iov = &iov;
+        mhdr.msg_iovlen = 1;
+        mhdr.msg_control = cmsgbuf;
+        mhdr.msg_controllen = cmsglen;
+
+        cc = sendmsg(sock->fd, &mhdr, a_ping_handle->ping_common.confirm);
+    }
+    a_ping_handle->ping_common.confirm = 0;
+
+    return (cc == len ? 0 : cc);
+}
+
+void pr_echo_reply(uint8_t *_icmph, int cc __attribute__((__unused__)))
+{
+    struct icmp6_hdr *icmph = (struct icmp6_hdr *) _icmph;
+    log_printf(" icmp_seq=%u", ntohs(icmph->icmp6_seq));
+}
+
+static void putchar_safe(char c)
+{
+    if(isprint(c))
+        putchar(c);
+    else
+        printf("\\%03o", c);
+}
+
+static
+void pr_niquery_reply_name(struct ni_hdr *nih, int len)
+{
+    uint8_t *h = (uint8_t *) (nih + 1);
+    uint8_t *p = h + 4;
+    uint8_t *end = (uint8_t *) nih + len;
+    int continued = 0;
+    char buf[1024];
+    int ret;
+
+    len -= sizeof(struct ni_hdr) + 4;
+
+    if(len < 0) {
+        printf(" parse error (too short)");
+        return;
+    }
+    while(p < end) {
+        int fqdn = 1;
+        size_t i;
+
+        memset(buf, 0xff, sizeof(buf));
+
+        if(continued)
+            putchar(',');
+
+        ret = dn_expand(h, end, p, buf, sizeof(buf));
+        if(ret < 0) {
+            printf(" parse error (truncated)");
+            break;
+        }
+        if(p + ret < end && *(p + ret) == '\0')
+            fqdn = 0;
+
+        putchar(' ');
+        for(i = 0; i < strlen(buf); i++)
+            putchar_safe(buf[i]);
+        if(fqdn)
+            putchar('.');
+
+        p += ret + !fqdn;
+
+        continued = 1;
+    }
+}
+
+static
+void pr_niquery_reply_addr(struct ni_hdr *nih, int len)
+{
+    uint8_t *h = (uint8_t *) (nih + 1);
+    uint8_t *p;
+    uint8_t *end = (uint8_t *) nih + len;
+    int af;
+    int aflen;
+    int continued = 0;
+    int truncated;
+    char buf[1024];
+
+    switch (ntohs(nih->ni_qtype)) {
+        case NI_QTYPE_IPV4ADDR:
+        af = AF_INET;
+        aflen = sizeof(struct in_addr);
+        truncated = nih->ni_flags & NI_IPV6ADDR_F_TRUNCATE;
+        break;
+        case NI_QTYPE_IPV6ADDR:
+        af = AF_INET6;
+        aflen = sizeof(struct in6_addr);
+        truncated = nih->ni_flags & NI_IPV4ADDR_F_TRUNCATE;
+        break;
+        default:
+        /* should not happen */
+        af = aflen = truncated = 0;
+    }
+    p = h;
+    if(len < 0) {
+        printf(" parse error (too short)");
+        return;
+    }
+
+    while(p < end) {
+        if(continued)
+            putchar(',');
+
+        if(p + sizeof(uint32_t) + aflen > end) {
+            printf(" parse error (truncated)");
+            break;
+        }
+        if(!inet_ntop(af, p + sizeof(uint32_t), buf, sizeof(buf)))
+            printf(" unexpeced error in inet_ntop(%s)",
+                    strerror(errno));
+        else
+            printf(" %s", buf);
+        p += sizeof(uint32_t) + aflen;
+
+        continued = 1;
+    }
+    if(truncated)
+        printf(" (truncated)");
+}
+
+static
+void pr_niquery_reply(uint8_t *_nih, int len)
+{
+    struct ni_hdr *nih = (struct ni_hdr *) _nih;
+
+    switch (nih->ni_code) {
+    case NI_SUCCESS:
+        switch (ntohs(nih->ni_qtype)) {
+            case NI_QTYPE_NAME:
+            pr_niquery_reply_name(nih, len);
+            break;
+            case NI_QTYPE_IPV4ADDR:
+            case NI_QTYPE_IPV6ADDR:
+            pr_niquery_reply_addr(nih, len);
+            break;
+            default:
+            printf(" unknown qtype(0x%02x)", ntohs(nih->ni_qtype));
+        }
+        break;
+        case NI_REFUSED:
+        printf(" refused");
+        break;
+        case NI_UNKNOWN:
+        printf(" unknown");
+        break;
+        default:
+        printf(" unknown code(%02x)", ntohs(nih->ni_code));
+    }
+    printf("; seq=%u;", ntohsp((uint16_t*) nih->ni_nonce));
+}
+
+/*
+ * parse_reply --
+ *  Print out the packet, if it came from us.  This logic is necessary
+ * because ALL readers of the ICMP socket get a copy of ALL ICMP packets
+ * which arrive ('tis only fair).  This permits multiple copies of this
+ * program to be run without having intermingled output (or statistics!).
+ */
+int
+ping6_parse_reply(ping_handle_t *a_ping_handle, socket_st *sock, struct msghdr *msg, int cc, void *addr, struct timeval *tv)
+{
+    struct sockaddr_in6 *from = addr;
+    uint8_t *buf = msg->msg_iov->iov_base;
+    struct cmsghdr *c;
+    struct icmp6_hdr *icmph;
+    int hops = -1;
+
+    for(c = CMSG_FIRSTHDR(msg); c; c = CMSG_NXTHDR(msg, c)) {
+        if(c->cmsg_level != IPPROTO_IPV6)
+            continue;
+        switch (c->cmsg_type) {
+        case IPV6_HOPLIMIT:
+            #ifdef IPV6_2292HOPLIMIT
+        case IPV6_2292HOPLIMIT:
+            #endif
+            if(c->cmsg_len < CMSG_LEN(sizeof(int)))
+                continue;
+            memcpy(&hops, CMSG_DATA(c), sizeof(hops));
+        }
+    }
+
+    /* Now the ICMP part */
+
+    icmph = (struct icmp6_hdr *) buf;
+    if(cc < 8) {
+        if(a_ping_handle->ping_common.options & F_VERBOSE)
+            error(0, 0, "packet too short: %d bytes", cc);
+        return 1;
+    }
+
+    if(icmph->icmp6_type == ICMP6_ECHO_REPLY) {
+        if(!is_ours(a_ping_handle, sock, icmph->icmp6_id))
+        return 1;
+        if (!contains_pattern_in_payload(a_ping_handle, (uint8_t*)(icmph+1)))
+        return 1; /* 'Twas really not our ECHO */
+        if (gather_statistics(a_ping_handle, (uint8_t*)icmph, sizeof(*icmph), cc,
+                ntohs(icmph->icmp6_seq),
+                hops, 0, tv, pr_addr(a_ping_handle, from, sizeof *from),
+                pr_echo_reply)) {
+            fflush(stdout);
+            return 0;
+        }
+    } else if (icmph->icmp6_type == ICMPV6_NI_REPLY) {
+        struct ni_hdr *nih = (struct ni_hdr *)icmph;
+        int seq = niquery_check_nonce(nih->ni_nonce);
+        if (seq < 0)
+        return 1;
+        if (gather_statistics(a_ping_handle, (uint8_t*)icmph, sizeof(*icmph), cc,
+                seq,
+                hops, 0, tv, pr_addr(a_ping_handle, from, sizeof *from),
+                pr_niquery_reply))
+        return 0;
+    } else {
+        int nexthdr;
+        struct ip6_hdr *iph1 = (struct ip6_hdr*)(icmph+1);
+        struct icmp6_hdr *icmph1 = (struct icmp6_hdr *)(iph1+1);
+
+        /* We must not ever fall here. All the messages but
+         * echo reply are blocked by filter and error are
+         * received with IPV6_RECVERR. Ugly code is preserved
+         * however, just to remember what crap we avoided
+         * using RECVRERR. :-)
+         */
+
+        if (cc < (int) (8 + sizeof(struct ip6_hdr) + 8))
+        return 1;
+
+        if (memcmp(&iph1->ip6_dst, &whereto.sin6_addr, 16))
+        return 1;
+
+        nexthdr = iph1->ip6_nxt;
+
+        if (nexthdr == 44) {
+            nexthdr = *(uint8_t*)icmph1;
+            icmph1++;
+        }
+        if (nexthdr == IPPROTO_ICMPV6) {
+            if (icmph1->icmp6_type != ICMP6_ECHO_REQUEST ||
+            !is_ours(a_ping_handle, sock, icmph1->icmp6_id))
+            return 1;
+            acknowledge(a_ping_handle, ntohs(icmph1->icmp6_seq));
+            a_ping_handle->ping_common.nerrors++;
+            if (a_ping_handle->ping_common.options & F_FLOOD) {
+                write_stdout("\bE", 2);
+                return 0;
+            }
+            print_timestamp(a_ping_handle);
+            printf("From %s: icmp_seq=%u ", pr_addr(a_ping_handle, from, sizeof *from), ntohs(icmph1->icmp6_seq));
+        } else {
+            /* We've got something other than an ECHOREPLY */
+            if (!(a_ping_handle->ping_common.options & F_VERBOSE) || a_ping_handle->ping_common.uid)
+            return 1;
+            print_timestamp(a_ping_handle);
+            printf("From %s: ", pr_addr(a_ping_handle, from, sizeof *from));
+        }
+        pr_icmph(icmph->icmp6_type, icmph->icmp6_code, ntohl(icmph->icmp6_mtu));
+    }
+
+    if(a_ping_handle->ping_common.options & F_AUDIBLE) {
+        putchar('\a');
+        if(a_ping_handle->ping_common.options & F_FLOOD)
+            fflush(stdout);
+    }
+    if(!(a_ping_handle->ping_common.options & F_FLOOD)) {
+        putchar('\n');
+        fflush(stdout);
+    }
+    return 0;
+}
+
+int pr_icmph(uint8_t type, uint8_t code, uint32_t info)
+{
+    switch (type) {
+    case ICMP6_DST_UNREACH:
+        printf("Destination unreachable: ");
+        switch (code) {
+        case ICMP6_DST_UNREACH_NOROUTE:
+            printf("No route");
+            break;
+        case ICMP6_DST_UNREACH_ADMIN:
+            printf("Administratively prohibited");
+            break;
+        case ICMP6_DST_UNREACH_BEYONDSCOPE:
+            printf("Beyond scope of source address");
+            break;
+        case ICMP6_DST_UNREACH_ADDR:
+            printf("Address unreachable");
+            break;
+        case ICMP6_DST_UNREACH_NOPORT:
+            printf("Port unreachable");
+            break;
+        default:
+            printf("Unknown code %d", code);
+            break;
+        }
+        break;
+    case ICMP6_PACKET_TOO_BIG:
+        printf("Packet too big: mtu=%u", info);
+        if(code)
+            printf(", code=%d", code);
+        break;
+    case ICMP6_TIME_EXCEEDED:
+        printf("Time exceeded: ");
+        if(code == ICMP6_TIME_EXCEED_TRANSIT)
+            printf("Hop limit");
+        else if(code == ICMP6_TIME_EXCEED_REASSEMBLY)
+            printf("Defragmentation failure");
+        else
+            printf("code %d", code);
+        break;
+    case ICMP6_PARAM_PROB:
+        printf("Parameter problem: ");
+        if(code == ICMP6_PARAMPROB_HEADER)
+            printf("Wrong header field ");
+        else if(code == ICMP6_PARAMPROB_NEXTHEADER)
+            printf("Unknown header ");
+        else if(code == ICMP6_PARAMPROB_OPTION)
+            printf("Unknown option ");
+        else
+            printf("code %d ", code);
+        printf("at %u", info);
+        break;
+    case ICMP6_ECHO_REQUEST:
+        printf("Echo request");
+        break;
+    case ICMP6_ECHO_REPLY:
+        printf("Echo reply");
+        break;
+    case MLD_LISTENER_QUERY:
+        printf("MLD Query");
+        break;
+    case MLD_LISTENER_REPORT:
+        printf("MLD Report");
+        break;
+    case MLD_LISTENER_REDUCTION:
+        printf("MLD Reduction");
+        break;
+    default:
+        printf("unknown icmp type: %u", type);
+
+    }
+    return 0;
+}
+
+void ping6_install_filter(ping_handle_t *a_ping_handle, socket_st *sock)
+{
+    static int once;
+    static struct sock_filter insns[] = {
+    BPF_STMT(BPF_LD|BPF_H|BPF_ABS, 4), /* Load icmp echo ident */
+    BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, 0xAAAA, 0, 1), /* Ours? */
+    BPF_STMT(BPF_RET|BPF_K, ~0U), /* Yes, it passes. */
+    BPF_STMT(BPF_LD|BPF_B|BPF_ABS, 0), /* Load icmp type */
+    BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, ICMP6_ECHO_REPLY, 1, 0), /* Echo? */
+    BPF_STMT(BPF_RET|BPF_K, ~0U), /* No. It passes. This must not happen. */
+    BPF_STMT(BPF_RET|BPF_K, 0), /* Echo with wrong ident. Reject. */
+    };
+    static struct sock_fprog filter = {
+        sizeof insns / sizeof(insns[0]),
+        insns
+    };
+
+    if(once)
+        return;
+    once = 1;
+
+    /* Patch bpflet for current identifier. */
+    insns[1] = (struct sock_filter )BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, htons(a_ping_handle->ping_common.ident), 0, 1);
+
+    if(setsockopt(sock->fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)))
+        error(0, errno, "WARNING: failed to install socket filter");
+}
diff --git a/libdap-chain-net/iputils/ping_common.c b/libdap-chain-net/iputils/ping_common.c
new file mode 100644
index 0000000000000000000000000000000000000000..566fdc755ee685ccff49f24f34d3450fb8590779
--- /dev/null
+++ b/libdap-chain-net/iputils/ping_common.c
@@ -0,0 +1,1030 @@
+/*
+ * Copyright (c) 1989 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Mike Muuss.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "ping.h"
+
+#ifndef HZ
+#define HZ sysconf(_SC_CLK_TCK)
+#endif
+
+/*
+int options;
+
+int mark;
+int sndbuf;
+int ttl;
+int rtt;
+int rtt_addend;
+uint16_t acked;
+
+unsigned char outpack[MAXPACKET];
+struct rcvd_table rcvd_tbl;
+
+// counters
+long npackets; // max packets to transmit
+long nreceived; // # of packets we got back
+long nrepeats; // number of duplicates
+long ntransmitted; // sequence # for outbound packets = #sent
+long nchecksum; // replies with bad checksum
+long nerrors; // icmp errors
+int interval = 1000; // interval between packets (msec)
+int preload = 1;
+int deadline = 0; // time to die
+int lingertime = MAXWAIT * 1000;
+struct timeval start_time, cur_time;
+int confirm = 0;
+volatile int in_pr_addr = 0; // pr_addr() is executing
+jmp_buf pr_addr_jmp;
+*/
+
+volatile int exiting;
+volatile int status_snapshot;
+
+/* Stupid workarounds for bugs/missing functionality in older linuces.
+ * confirm_flag fixes refusing service of kernels without MSG_CONFIRM.
+ * i.e. for linux-2.2 */
+/*
+
+int confirm_flag = MSG_CONFIRM;
+
+// timing
+int timing; // flag to do timing
+long tmin = LONG_MAX; // minimum round trip time
+long tmax; // maximum round trip time
+*/
+/* Message for rpm maintainers: have _shame_. If you want
+ * to fix something send the patch to me for sanity checking.
+ * "sparcfix" patch is a complete non-sense, apparenly the person
+ * prepared it was stoned.
+ */
+/*
+long long tsum; // sum of all times, for doing average
+long long tsum2;
+int pipesize = -1;
+
+int datalen = DEFDATALEN;
+
+char *hostname;
+int uid;
+uid_t euid;
+int ident; // process id to identify our packets
+
+static int screen_width = INT_MAX;
+
+#ifdef HAVE_LIBCAP
+static cap_value_t cap_raw = CAP_NET_RAW;
+static cap_value_t cap_admin = CAP_NET_ADMIN;
+#endif
+*/
+
+#define ARRAY_SIZE(a)	(sizeof(a) / sizeof(a[0]))
+
+
+void usage(void)
+{
+    fprintf(stderr,
+            "\nUsage\n"
+                    "  ping [options] <destination>\n"
+                    "\nOptions:\n"
+                    "  <destination>      dns name or ip address\n"
+                    "  -a                 use audible ping\n"
+                    "  -A                 use adaptive ping\n"
+                    "  -B                 sticky source address\n"
+                    "  -c <count>         stop after <count> replies\n"
+                    "  -D                 print timestamps\n"
+                    "  -d                 use SO_DEBUG socket option\n"
+                    "  -f                 flood ping\n"
+                    "  -h                 print help and exit\n"
+                    "  -I <interface>     either interface name or address\n"
+                    "  -i <interval>      seconds between sending each packet\n"
+                    "  -L                 suppress loopback of multicast packets\n"
+                    "  -l <preload>       send <preload> number of packages while waiting replies\n"
+                    "  -m <mark>          tag the packets going out\n"
+                    "  -M <pmtud opt>     define mtu discovery, can be one of <do|dont|want>\n"
+                    "  -n                 no dns name resolution\n"
+                    "  -O                 report outstanding replies\n"
+                    "  -p <pattern>       contents of padding byte\n"
+                    "  -q                 quiet output\n"
+                    "  -Q <tclass>        use quality of service <tclass> bits\n"
+                    "  -s <size>          use <size> as number of data bytes to be sent\n"
+                    "  -S <size>          use <size> as SO_SNDBUF socket option value\n"
+                    "  -t <ttl>           define time to live\n"
+                    "  -U                 print user-to-user latency\n"
+                    "  -v                 verbose output\n"
+                    "  -V                 print version and exit\n"
+                    "  -w <deadline>      reply wait <deadline> in seconds\n"
+                    "  -W <timeout>       time to wait for response\n"
+                    "\nIPv4 options:\n"
+                    "  -4                 use IPv4\n"
+                    "  -b                 allow pinging broadcast\n"
+                    "  -R                 record route\n"
+                    "  -T <timestamp>     define timestamp, can be one of <tsonly|tsandaddr|tsprespec>\n"
+                    "\nIPv6 options:\n"
+                    "  -6                 use IPv6\n"
+                    "  -F <flowlabel>     define flow label, default is random\n"
+                    "  -N <nodeinfo opt>  use icmp6 node info query, try <help> as argument\n"
+                    "\nFor more details see ping(8).\n"
+            );
+    //exit(2);
+    exit(2);
+}
+
+void limit_capabilities(ping_handle_t *a_ping_handle)
+{
+#ifdef HAVE_LIBCAP
+    cap_t cap_cur_p;
+    cap_t cap_p;
+    cap_flag_value_t cap_ok;
+
+    cap_cur_p = cap_get_proc();
+    if (!cap_cur_p)
+    error(-1, errno, "cap_get_proc");
+    cap_p = cap_init();
+    if (!cap_p)
+    error(-1, errno, "cap_init");
+    cap_ok = CAP_CLEAR;
+    cap_get_flag(cap_cur_p, CAP_NET_ADMIN, CAP_PERMITTED, &cap_ok);
+    if (cap_ok != CAP_CLEAR)
+    cap_set_flag(cap_p, CAP_PERMITTED, 1, &cap_admin, CAP_SET);
+    cap_ok = CAP_CLEAR;
+    cap_get_flag(cap_cur_p, CAP_NET_RAW, CAP_PERMITTED, &cap_ok);
+    if (cap_ok != CAP_CLEAR)
+    cap_set_flag(cap_p, CAP_PERMITTED, 1, &cap_raw, CAP_SET);
+    if (cap_set_proc(cap_p) < 0)
+    error(-1, errno, "cap_set_proc");
+    if (prctl(PR_SET_KEEPCAPS, 1) < 0)
+    error(-1, errno, "prctl");
+    if (setuid(getuid()) < 0)
+    error(-1, errno, "setuid");
+    if (prctl(PR_SET_KEEPCAPS, 0) < 0)
+    error(-1, errno, "prctl");
+    cap_free(cap_p);
+    cap_free(cap_cur_p);
+#endif
+    a_ping_handle->ping_common.uid = getuid();
+    a_ping_handle->ping_common.euid = geteuid();
+#ifndef HAVE_LIBCAP
+    if(seteuid(a_ping_handle->ping_common.uid))
+        error(-1, errno, "setuid");
+#endif
+}
+
+#ifdef HAVE_LIBCAP
+int modify_capability(cap_value_t cap, cap_flag_value_t on)
+{
+    cap_t cap_p = cap_get_proc();
+    cap_flag_value_t cap_ok;
+    int rc = -1;
+
+    if (!cap_p) {
+        error(0, errno, "cap_get_proc");
+        goto out;
+    }
+
+    cap_ok = CAP_CLEAR;
+    cap_get_flag(cap_p, cap, CAP_PERMITTED, &cap_ok);
+    if (cap_ok == CAP_CLEAR) {
+        rc = on ? -1 : 0;
+        goto out;
+    }
+
+    cap_set_flag(cap_p, CAP_EFFECTIVE, 1, &cap, on);
+
+    if (cap_set_proc(cap_p) < 0) {
+        error(0, errno, "cap_set_proc");
+        goto out;
+    }
+
+    cap_free(cap_p);
+    cap_p = NULL;
+
+    rc = 0;
+    out:
+    if (cap_p)
+    cap_free(cap_p);
+    return rc;
+}
+#else
+int modify_capability(ping_handle_t *a_ping_handle, int on)
+{
+    if(seteuid(on ? a_ping_handle->ping_common.euid : getuid())) {
+        error(0, errno, "seteuid");
+        return -1;
+    }
+
+    return 0;
+}
+#endif
+
+void drop_capabilities(void)
+{
+#ifdef HAVE_LIBCAP
+    cap_t cap = cap_init();
+    if (cap_set_proc(cap) < 0)
+    error(-1, errno, "cap_set_proc");
+    cap_free(cap);
+#else
+    if(setuid(getuid()))
+        error(-1, errno, "setuid");
+#endif
+}
+
+/* Fills all the outpack, excluding ICMP header, but _including_
+ * timestamp area with supplied pattern.
+ */
+void fill(ping_handle_t *a_ping_handle, char *patp, unsigned char *packet, unsigned packet_size)
+{
+    int ii, jj;
+    unsigned int pat[16];
+    char *cp;
+    unsigned char *bp = packet + 8;
+
+#ifdef USE_IDN
+    setlocale(LC_ALL, "C");
+#endif
+
+    for(cp = patp; *cp; cp++) {
+        if(!isxdigit(*cp))
+            error(2, 0, "patterns must be specified as hex digits: %s", cp);
+    }
+    ii = sscanf(patp,
+            "%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x",
+            &pat[0], &pat[1], &pat[2], &pat[3], &pat[4], &pat[5], &pat[6],
+            &pat[7], &pat[8], &pat[9], &pat[10], &pat[11], &pat[12],
+            &pat[13], &pat[14], &pat[15]);
+
+    if(ii > 0) {
+        unsigned kk;
+        for(kk = 0; kk <= packet_size - (8 + ii); kk += ii)
+            for(jj = 0; jj < ii; ++jj)
+                bp[jj + kk] = pat[jj];
+    }
+    if(!(a_ping_handle->ping_common.options & F_QUIET)) {
+        printf("PATTERN: 0x");
+        for(jj = 0; jj < ii; ++jj)
+            printf("%02x", bp[jj] & 0xFF);
+        printf("\n");
+    }
+
+#ifdef USE_IDN
+    setlocale(LC_ALL, "");
+#endif
+}
+
+static void sigexit(int signo __attribute__((__unused__)))
+{
+    //exiting = 1;
+    //if(in_pr_addr)
+    //    longjmp(pr_addr_jmp, 0);
+}
+
+static void sigstatus(int signo __attribute__((__unused__)))
+{
+    //status_snapshot = 1;
+}
+
+int __schedule_exit(ping_handle_t *a_ping_handle, int next)
+{
+    static unsigned long waittime;
+    struct itimerval it;
+
+    if(waittime)
+        return next;
+
+    if(a_ping_handle->ping_common.nreceived) {
+        waittime = 2 * a_ping_handle->ping_common.tmax;
+        if(waittime < (unsigned long) (1000 * a_ping_handle->ping_common.interval))
+            waittime = 1000 * a_ping_handle->ping_common.interval;
+    } else
+        waittime = a_ping_handle->ping_common.lingertime * 1000;
+
+    if(next < 0 || (unsigned long) next < waittime / 1000)
+        next = waittime / 1000;
+
+    it.it_interval.tv_sec = 0;
+    it.it_interval.tv_usec = 0;
+    it.it_value.tv_sec = waittime / 1000000;
+    it.it_value.tv_usec = waittime % 1000000;
+    setitimer(ITIMER_REAL, &it, NULL);
+    return next;
+}
+
+static inline void update_interval(ping_handle_t *a_ping_handle)
+{
+    int est = a_ping_handle->ping_common.rtt ? a_ping_handle->ping_common.rtt / 8 : a_ping_handle->ping_common.interval * 1000;
+
+    a_ping_handle->ping_common.interval = (est + a_ping_handle->ping_common.rtt_addend + 500) / 1000;
+    if(a_ping_handle->ping_common.uid && a_ping_handle->ping_common.interval < MINUSERINTERVAL)
+        a_ping_handle->ping_common.interval = MINUSERINTERVAL;
+}
+
+/*
+ * Print timestamp
+ */
+void print_timestamp(ping_handle_t *a_ping_handle)
+{
+    if(a_ping_handle->ping_common.options & F_PTIMEOFDAY) {
+        struct timeval tv;
+        gettimeofday(&tv, NULL);
+        printf("[%lu.%06lu] ",
+                (unsigned long) tv.tv_sec, (unsigned long) tv.tv_usec);
+    }
+}
+
+/*
+ * pinger --
+ * 	Compose and transmit an ICMP ECHO REQUEST packet.  The IP packet
+ * will be added on by the kernel.  The ID field is our UNIX process ID,
+ * and the sequence number is an ascending integer.  The first several bytes
+ * of the data portion are used to hold a UNIX "timeval" struct in VAX
+ * byte-order, to compute the round-trip time.
+ */
+int pinger(ping_handle_t *a_ping_handle, ping_func_set_st *fset, socket_st *sock)
+{
+    static int oom_count;
+    static int tokens;
+    int i;
+
+    /* Have we already sent enough? If we have, return an arbitrary positive value. */
+    if(exiting || (a_ping_handle->ping_common.npackets && a_ping_handle->ping_common.ntransmitted >= a_ping_handle->ping_common.npackets && !a_ping_handle->ping_common.deadline))
+        return 1000;
+
+    /* Check that packets < rate*time + preload */
+    if(a_ping_handle->ping_common.cur_time.tv_sec == 0) {
+        gettimeofday(&a_ping_handle->ping_common.cur_time, NULL);
+        tokens = a_ping_handle->ping_common.interval * (a_ping_handle->ping_common.preload - 1);
+    } else {
+        long ntokens;
+        struct timeval tv;
+
+        gettimeofday(&tv, NULL);
+        ntokens = (tv.tv_sec - a_ping_handle->ping_common.cur_time.tv_sec) * 1000 +
+                (tv.tv_usec - a_ping_handle->ping_common.cur_time.tv_usec) / 1000;
+        if(!a_ping_handle->ping_common.interval) {
+            /* Case of unlimited flood is special;
+             * if we see no reply, they are limited to 100pps */
+            if(ntokens < MININTERVAL && in_flight(a_ping_handle) >= a_ping_handle->ping_common.preload)
+                return MININTERVAL - ntokens;
+        }
+        ntokens += tokens;
+        if(ntokens > a_ping_handle->ping_common.interval * a_ping_handle->ping_common.preload)
+            ntokens = a_ping_handle->ping_common.interval * a_ping_handle->ping_common.preload;
+        if(ntokens < a_ping_handle->ping_common.interval)
+            return a_ping_handle->ping_common.interval - ntokens;
+
+        a_ping_handle->ping_common.cur_time = tv;
+        tokens = ntokens - a_ping_handle->ping_common.interval;
+    }
+
+    if(a_ping_handle->ping_common.options & F_OUTSTANDING) {
+        if(a_ping_handle->ping_common.ntransmitted > 0 && !rcvd_test(a_ping_handle, a_ping_handle->ping_common.ntransmitted)) {
+            print_timestamp(a_ping_handle);
+            printf("no answer yet for icmp_seq=%lu\n", (a_ping_handle->ping_common.ntransmitted % MAX_DUP_CHK));
+            fflush(stdout);
+        }
+    }
+
+    resend:
+    i = fset->send_probe(a_ping_handle, sock, a_ping_handle->ping_common.outpack, sizeof(a_ping_handle->ping_common.outpack));
+
+    if(i == 0) {
+        oom_count = 0;
+        advance_ntransmitted(a_ping_handle);
+        if(!(a_ping_handle->ping_common.options & F_QUIET) && (a_ping_handle->ping_common.options & F_FLOOD)) {
+            /* Very silly, but without this output with
+             * high preload or pipe size is very confusing. */
+            if((a_ping_handle->ping_common.preload < a_ping_handle->ping_common.screen_width && a_ping_handle->ping_common.pipesize < a_ping_handle->ping_common.screen_width) ||
+                    in_flight(a_ping_handle) < a_ping_handle->ping_common.screen_width)
+                write_stdout(".", 1);
+        }
+        return a_ping_handle->ping_common.interval - tokens;
+    }
+
+    /* And handle various errors... */
+    if(i > 0) {
+        /* Apparently, it is some fatal bug. */
+        abort();
+    } else if(errno == ENOBUFS || errno == ENOMEM) {
+        int nores_interval;
+
+        /* Device queue overflow or OOM. Packet is not sent. */
+        tokens = 0;
+        /* Slowdown. This works only in adaptive mode (option -A) */
+        a_ping_handle->ping_common.rtt_addend += (a_ping_handle->ping_common.rtt < 8 * 50000 ? a_ping_handle->ping_common.rtt / 8 : 50000);
+        if(a_ping_handle->ping_common.options & F_ADAPTIVE)
+            update_interval(a_ping_handle);
+        nores_interval = SCHINT(a_ping_handle->ping_common.interval / 2);
+        if(nores_interval > 500)
+            nores_interval = 500;
+        oom_count++;
+        if(oom_count * nores_interval < a_ping_handle->ping_common.lingertime)
+            return nores_interval;
+        i = 0;
+        /* Fall to hard error. It is to avoid complete deadlock
+         * on stuck output device even when dealine was not requested.
+         * Expected timings are screwed up in any case, but we will
+         * exit some day. :-) */
+    } else if(errno == EAGAIN) {
+        /* Socket buffer is full. */
+        tokens += a_ping_handle->ping_common.interval;
+        return MININTERVAL;
+    } else {
+        if((i = fset->receive_error_msg(a_ping_handle, sock)) > 0) {
+            /* An ICMP error arrived. In this case, we've received
+             * an error from sendto(), but we've also received an
+             * ICMP message, which means the packet did in fact
+             * send in some capacity. So, in this odd case, report
+             * the more specific errno as the error, and treat this
+             * as a hard local error. */
+            i = 0;
+            goto hard_local_error;
+        }
+        /* Compatibility with old linuces. */
+        if(i == 0 && a_ping_handle->ping_common.confirm_flag && errno == EINVAL) {
+            a_ping_handle->ping_common.confirm_flag = 0;
+            errno = 0;
+        }
+        if(!errno)
+            goto resend;
+    }
+
+    hard_local_error:
+    /* Hard local error. Pretend we sent packet. */
+    advance_ntransmitted(a_ping_handle);
+
+    if(i == 0 && !(a_ping_handle->ping_common.options & F_QUIET)) {
+        if(a_ping_handle->ping_common.options & F_FLOOD)
+            write_stdout("E", 1);
+        else
+            perror("ping: sendmsg");
+    }
+    tokens = 0;
+    return SCHINT(a_ping_handle->ping_common.interval);
+}
+
+/* Set socket buffers, "alloc" is an estimate of memory taken by single packet. */
+
+void sock_setbufs(ping_handle_t *a_ping_handle, socket_st *sock, int alloc)
+{
+    int rcvbuf, hold;
+    socklen_t tmplen = sizeof(hold);
+
+    if(!a_ping_handle->ping_common.sndbuf)
+        a_ping_handle->ping_common.sndbuf = alloc;
+    setsockopt(sock->fd, SOL_SOCKET, SO_SNDBUF, (char *) &a_ping_handle->ping_common.sndbuf, sizeof(a_ping_handle->ping_common.sndbuf));
+
+    rcvbuf = hold = alloc * a_ping_handle->ping_common.preload;
+    if(hold < 65536)
+        hold = 65536;
+    setsockopt(sock->fd, SOL_SOCKET, SO_RCVBUF, (char *) &hold, sizeof(hold));
+    if(getsockopt(sock->fd, SOL_SOCKET, SO_RCVBUF, (char *) &hold, &tmplen) == 0) {
+        if(hold < rcvbuf)
+            error(0, 0, "WARNING: probably, rcvbuf is not enough to hold preload");
+    }
+}
+
+/* Protocol independent setup and parameter checks. */
+
+void setup(ping_handle_t *a_ping_handle, socket_st *sock)
+{
+    int hold;
+    struct timeval tv;
+    sigset_t sset;
+
+    if((a_ping_handle->ping_common.options & F_FLOOD) && !(a_ping_handle->ping_common.options & F_INTERVAL))
+        a_ping_handle->ping_common.interval = 0;
+
+    if(a_ping_handle->ping_common.uid && a_ping_handle->ping_common.interval < MINUSERINTERVAL)
+        error(2, 0, "cannot flood; minimal interval allowed for user is %dms", MINUSERINTERVAL);
+
+    if(a_ping_handle->ping_common.interval >= INT_MAX / a_ping_handle->ping_common.preload)
+        error(2, 0, "illegal preload and/or interval: %d", a_ping_handle->ping_common.interval);
+
+    hold = 1;
+    if(a_ping_handle->ping_common.options & F_SO_DEBUG)
+        setsockopt(sock->fd, SOL_SOCKET, SO_DEBUG, (char *) &hold, sizeof(hold));
+    if(a_ping_handle->ping_common.options & F_SO_DONTROUTE)
+        setsockopt(sock->fd, SOL_SOCKET, SO_DONTROUTE, (char *) &hold, sizeof(hold));
+
+#ifdef SO_TIMESTAMP
+    if(!(a_ping_handle->ping_common.options & F_LATENCY)) {
+        int on = 1;
+        if(setsockopt(sock->fd, SOL_SOCKET, SO_TIMESTAMP, &on, sizeof(on)))
+            error(0, 0, "Warning: no SO_TIMESTAMP support, falling back to SIOCGSTAMP");
+    }
+#endif
+#ifdef SO_MARK
+    if(a_ping_handle->ping_common.options & F_MARK) {
+        int ret;
+        int errno_save;
+
+        enable_capability_admin(a_ping_handle);
+        ret = setsockopt(sock->fd, SOL_SOCKET, SO_MARK, &a_ping_handle->ping_common.mark, sizeof(a_ping_handle->ping_common.mark));
+        errno_save = errno;
+        disable_capability_admin(a_ping_handle);
+
+        if(ret == -1) {
+            /* we probably dont wanna exit since old kernels
+             * dont support mark ..
+             */
+            error(0, errno_save, "Warning: Failed to set mark: %d", a_ping_handle->ping_common.mark);
+        }
+    }
+#endif
+
+    /* Set some SNDTIMEO to prevent blocking forever
+     * on sends, when device is too slow or stalls. Just put limit
+     * of one second, or "interval", if it is less.
+     */
+    tv.tv_sec = 1;
+    tv.tv_usec = 0;
+    if(a_ping_handle->ping_common.interval < 1000) {
+        tv.tv_sec = 0;
+        tv.tv_usec = 1000 * SCHINT(a_ping_handle->ping_common.interval);
+    }
+    setsockopt(sock->fd, SOL_SOCKET, SO_SNDTIMEO, (char*) &tv, sizeof(tv));
+
+    /* Set RCVTIMEO to "interval". Note, it is just an optimization
+     * allowing to avoid redundant poll(). */
+    tv.tv_sec = SCHINT(a_ping_handle->ping_common.interval) / 1000;
+    tv.tv_usec = 1000 * (SCHINT(a_ping_handle->ping_common.interval) % 1000);
+    if(setsockopt(sock->fd, SOL_SOCKET, SO_RCVTIMEO, (char*) &tv, sizeof(tv)))
+        a_ping_handle->ping_common.options |= F_FLOOD_POLL;
+
+    if(!(a_ping_handle->ping_common.options & F_PINGFILLED)) {
+        int i;
+        unsigned char *p = a_ping_handle->ping_common.outpack + 8;
+
+        /* Do not forget about case of small datalen,
+         * fill timestamp area too!
+         */
+        for(i = 0; i < a_ping_handle->ping_common.datalen; ++i)
+            *p++ = i;
+    }
+
+    if(sock->socktype == SOCK_RAW)
+        a_ping_handle->ping_common.ident = htons(getpid() & 0xFFFF);
+
+    set_signal(SIGINT, sigexit);
+    set_signal(SIGALRM, sigexit);
+    set_signal(SIGQUIT, sigstatus);
+
+    sigemptyset(&sset);
+    sigprocmask(SIG_SETMASK, &sset, NULL);
+
+    gettimeofday(&a_ping_handle->ping_common.start_time, NULL);
+
+    if(a_ping_handle->ping_common.deadline) {
+        struct itimerval it;
+
+        it.it_interval.tv_sec = 0;
+        it.it_interval.tv_usec = 0;
+        it.it_value.tv_sec = a_ping_handle->ping_common.deadline;
+        it.it_value.tv_usec = 0;
+        setitimer(ITIMER_REAL, &it, NULL);
+    }
+
+    if(isatty(STDOUT_FILENO)) {
+        struct winsize w;
+
+        if(ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) != -1) {
+            if(w.ws_col > 0)
+                a_ping_handle->ping_common.screen_width = w.ws_col;
+        }
+    }
+}
+
+/*
+ * Return 0 if pattern in payload point to be ptr did not match the pattern that was sent  
+ */
+int contains_pattern_in_payload(ping_handle_t *a_ping_handle, uint8_t *ptr)
+{
+    int i;
+    uint8_t *cp, *dp;
+
+    /* check the data */
+    cp = ((u_char*) ptr) + sizeof(struct timeval);
+    dp = &a_ping_handle->ping_common.outpack[8 + sizeof(struct timeval)];
+    for(i = sizeof(struct timeval); i < a_ping_handle->ping_common.datalen; ++i, ++cp, ++dp) {
+        if(*cp != *dp)
+            return 0;
+    }
+    return 1;
+}
+
+void main_loop(ping_handle_t *a_ping_handle, ping_func_set_st *fset, socket_st *sock, uint8_t *packet, int packlen)
+{
+    char addrbuf[128];
+    char ans_data[4096];
+    struct iovec iov;
+    struct msghdr msg;
+    struct cmsghdr *c;
+    int cc;
+    int next;
+    int polling;
+    int recv_error;
+
+    iov.iov_base = (char *) packet;
+
+    for(;;) {
+        /* Check exit conditions. */
+        if(exiting)
+            break;
+        if(a_ping_handle->ping_common.npackets && a_ping_handle->ping_common.nreceived + a_ping_handle->ping_common.nerrors >= a_ping_handle->ping_common.npackets)
+            break;
+        if(a_ping_handle->ping_common.deadline && a_ping_handle->ping_common.nerrors)
+            break;
+        /* Check for and do special actions. */
+        if(status_snapshot)
+            status(a_ping_handle);
+
+        /* Send probes scheduled to this time. */
+        do {
+            next = pinger(a_ping_handle, fset, sock);
+            next = schedule_exit(a_ping_handle, next);
+        } while(next <= 0);
+
+        /* "next" is time to send next probe, if positive.
+         * If next<=0 send now or as soon as possible. */
+
+        /* Technical part. Looks wicked. Could be dropped,
+         * if everyone used the newest kernel. :-)
+         * Its purpose is:
+         * 1. Provide intervals less than resolution of scheduler.
+         *    Solution: spinning.
+         * 2. Avoid use of poll(), when recvmsg() can provide
+         *    timed waiting (SO_RCVTIMEO). */
+        polling = 0;
+        recv_error = 0;
+        if((a_ping_handle->ping_common.options & (F_ADAPTIVE | F_FLOOD_POLL)) || next < SCHINT(a_ping_handle->ping_common.interval)) {
+            int recv_expected = in_flight(a_ping_handle);
+
+            /* If we are here, recvmsg() is unable to wait for
+             * required timeout. */
+            if(1000 % HZ == 0 ? next <= 1000 / HZ : (next < INT_MAX / HZ && next * HZ <= 1000)) {
+                /* Very short timeout... So, if we wait for
+                 * something, we sleep for MININTERVAL.
+                 * Otherwise, spin! */
+                if(recv_expected) {
+                    next = MININTERVAL;
+                } else {
+                    next = 0;
+                    /* When spinning, no reasons to poll.
+                     * Use nonblocking recvmsg() instead. */
+                    polling = MSG_DONTWAIT;
+                    /* But yield yet. */
+                    sched_yield();
+                }
+            }
+
+            if(!polling &&
+                    ((a_ping_handle->ping_common.options & (F_ADAPTIVE | F_FLOOD_POLL)) || a_ping_handle->ping_common.interval)) {
+                struct pollfd pset;
+                pset.fd = sock->fd;
+                pset.events = POLLIN;
+                pset.revents = 0;
+                if(poll(&pset, 1, next) < 1 ||
+                        !(pset.revents & (POLLIN | POLLERR)))
+                    continue;
+                polling = MSG_DONTWAIT;
+                recv_error = pset.revents & POLLERR;
+            }
+        }
+
+        for(;;) {
+            struct timeval *recv_timep = NULL;
+            struct timeval recv_time;
+            int not_ours = 0; /* Raw socket can receive messages
+             * destined to other running pings. */
+
+            iov.iov_len = packlen;
+            memset(&msg, 0, sizeof(msg));
+            msg.msg_name = addrbuf;
+            msg.msg_namelen = sizeof(addrbuf);
+            msg.msg_iov = &iov;
+            msg.msg_iovlen = 1;
+            msg.msg_control = ans_data;
+            msg.msg_controllen = sizeof(ans_data);
+
+            cc = recvmsg(sock->fd, &msg, polling);
+            //log_printf("**recvmsg(fd=%d,msg=0x%x,polling=%d)=%d\n",sock->fd,&msg,polling,cc);
+            polling = MSG_DONTWAIT;
+
+            if(cc < 0) {
+                /* If there was a POLLERR and there is no packet
+                 * on the socket, try to read the error queue.
+                 * Otherwise, give up.
+                 */
+                if((errno == EAGAIN && !recv_error) ||
+                errno == EINTR)
+                    break;
+                recv_error = 0;
+                if(!fset->receive_error_msg(a_ping_handle, sock)) {
+                    if(errno) {
+                        error(0, errno, "recvmsg");
+                        break;
+                    }
+                    not_ours = 1;
+                }
+            } else {
+
+#ifdef SO_TIMESTAMP
+                for(c = CMSG_FIRSTHDR(&msg); c; c = CMSG_NXTHDR(&msg, c)) {
+                    if(c->cmsg_level != SOL_SOCKET ||
+                            c->cmsg_type != SO_TIMESTAMP)
+                        continue;
+                    if(c->cmsg_len < CMSG_LEN(sizeof(struct timeval)))
+                        continue;
+                    recv_timep = (struct timeval*) CMSG_DATA(c);
+                }
+#endif
+
+                if((a_ping_handle->ping_common.options & F_LATENCY) || recv_timep == NULL) {
+                    if((a_ping_handle->ping_common.options & F_LATENCY) ||
+                            ioctl(sock->fd, SIOCGSTAMP, &recv_time))
+                        gettimeofday(&recv_time, NULL);
+                    recv_timep = &recv_time;
+                }
+                not_ours = fset->parse_reply(a_ping_handle, sock, &msg, cc, addrbuf, recv_timep);
+            }
+
+            /* See? ... someone runs another ping on this host. */
+            if(not_ours && sock->socktype == SOCK_RAW)
+                fset->install_filter(a_ping_handle, sock);
+
+            /* If nothing is in flight, "break" returns us to pinger. */
+            if(in_flight(a_ping_handle) == 0)
+                break;
+
+            /* Otherwise, try to recvmsg() again. recvmsg()
+             * is nonblocking after the first iteration, so that
+             * if nothing is queued, it will receive EAGAIN
+             * and return to pinger. */
+        }
+    }
+    // here present exit() from app
+    finish(a_ping_handle);
+}
+
+int gather_statistics(ping_handle_t *a_ping_handle, uint8_t *icmph, int icmplen,
+        int cc, uint16_t seq, int hops,
+        int csfailed, struct timeval *tv, char *from,
+        void (*pr_reply)(uint8_t *icmph, int cc))
+{
+    int dupflag = 0;
+    long triptime = 0;
+    uint8_t *ptr = icmph + icmplen;
+
+    ++a_ping_handle->ping_common.nreceived;
+    if(!csfailed)
+        acknowledge(a_ping_handle, seq);
+
+    if(a_ping_handle->ping_common.timing && cc >= (int) (8 + sizeof(struct timeval))) {
+        struct timeval tmp_tv;
+        memcpy(&tmp_tv, ptr, sizeof(tmp_tv));
+
+        restamp:
+        tvsub(tv, &tmp_tv);
+        triptime = tv->tv_sec * 1000000 + tv->tv_usec;
+        if(triptime < 0) {
+            error(0, 0, "Warning: time of day goes back (%ldus), taking countermeasures", triptime);
+            triptime = 0;
+            if(!(a_ping_handle->ping_common.options & F_LATENCY)) {
+                gettimeofday(tv, NULL);
+                a_ping_handle->ping_common.options |= F_LATENCY;
+                goto restamp;
+            }
+        }
+        if(!csfailed) {
+            a_ping_handle->ping_common.tsum += triptime;
+            a_ping_handle->ping_common.tsum2 += (long long) triptime * (long long) triptime;
+            if(triptime < a_ping_handle->ping_common.tmin)
+                a_ping_handle->ping_common.tmin = triptime;
+            if(triptime > a_ping_handle->ping_common.tmax)
+                a_ping_handle->ping_common.tmax = triptime;
+            if(!a_ping_handle->ping_common.rtt)
+                a_ping_handle->ping_common.rtt = triptime * 8;
+            else
+                a_ping_handle->ping_common.rtt += triptime - a_ping_handle->ping_common.rtt / 8;
+            if(a_ping_handle->ping_common.options & F_ADAPTIVE)
+                update_interval(a_ping_handle);
+        }
+    }
+
+    if(csfailed) {
+        ++a_ping_handle->ping_common.nchecksum;
+        --a_ping_handle->ping_common.nreceived;
+    } else if(rcvd_test(a_ping_handle, seq)) {
+        ++a_ping_handle->ping_common.nrepeats;
+        --a_ping_handle->ping_common.nreceived;
+        dupflag = 1;
+    } else {
+        rcvd_set(a_ping_handle, seq);
+        dupflag = 0;
+    }
+    a_ping_handle->ping_common.confirm = a_ping_handle->ping_common.confirm_flag;
+
+    if(a_ping_handle->ping_common.options & F_QUIET)
+        return 1;
+
+    if(a_ping_handle->ping_common.options & F_FLOOD) {
+        if(!csfailed)
+            write_stdout("\b \b", 3);
+        else
+            write_stdout("\bC", 2);
+    } else {
+        int i;
+        uint8_t *cp, *dp;
+
+        print_timestamp(a_ping_handle);
+        log_printf("%d bytes from %s:", cc, from);
+
+        if(pr_reply)
+            pr_reply(icmph, cc);
+
+        if(hops >= 0)
+            log_printf(" ttl=%d", hops);
+
+        if(cc < a_ping_handle->ping_common.datalen + 8) {
+            log_printf(" (truncated)\n");
+            return 1;
+        }
+        if(a_ping_handle->ping_common.timing) {
+            if(triptime >= 100000)
+                log_printf(" time=%ld ms", (triptime + 500) / 1000);
+            else if(triptime >= 10000)
+                log_printf(" time=%ld.%01ld ms", (triptime + 50) / 1000,
+                        ((triptime + 50) % 1000) / 100);
+            else if(triptime >= 1000)
+                log_printf(" time=%ld.%02ld ms", (triptime + 5) / 1000,
+                        ((triptime + 5) % 1000) / 10);
+            else
+                log_printf(" time=%ld.%03ld ms", triptime / 1000,
+                        triptime % 1000);
+            log_printf(" tsum=%d ", a_ping_handle->ping_common.tsum);
+        }
+        if(dupflag)
+            log_printf(" (DUP!)");
+        if(csfailed)
+            log_printf(" (BAD CHECKSUM!)");
+
+        /* check the data */
+        cp = ((unsigned char*) ptr) + sizeof(struct timeval);
+        dp = &a_ping_handle->ping_common.outpack[8 + sizeof(struct timeval)];
+        for(i = sizeof(struct timeval); i < a_ping_handle->ping_common.datalen; ++i, ++cp, ++dp) {
+            if(*cp != *dp) {
+                log_printf("\nwrong data byte #%d should be 0x%x but was 0x%x",
+                        i, *dp, *cp);
+                cp = (unsigned char*) ptr + sizeof(struct timeval);
+                for(i = sizeof(struct timeval); i < a_ping_handle->ping_common.datalen; ++i, ++cp) {
+                    if((i % 32) == sizeof(struct timeval))
+                        log_printf("\n#%d\t", i);
+                    log_printf("%x ", *cp);
+                }
+                break;
+            }
+        }
+    }
+    return 0;
+}
+#ifdef PING_DBG
+static long llsqrt(long long a)
+{
+    long long prev = LLONG_MAX;
+    long long x = a;
+
+    if (x > 0) {
+        while (x < prev) {
+            prev = x;
+            x = (x+(a/x))/2;
+        }
+    }
+
+    return (long)x;
+}
+#endif
+
+/*
+ * finish --
+ *	Print out statistics, and give up.
+ */
+void finish(ping_handle_t *a_ping_handle)
+{
+    struct timeval tv = a_ping_handle->ping_common.cur_time;
+#ifdef PING_DBG
+    char *comma = "";
+#endif
+
+    tvsub(&tv, &a_ping_handle->ping_common.start_time);
+#ifdef PING_DBG
+    putchar('\n');
+    fflush(stdout);
+    printf("--- %s ping statistics ---\n", hostname);
+    printf("%ld packets transmitted, ", ntransmitted);
+    printf("%ld received", nreceived);
+    if (nrepeats)
+    log_printf(", +%ld duplicates", nrepeats);
+    if (nchecksum)
+    printf(", +%ld corrupted", nchecksum);
+    if (nerrors)
+    printf(", +%ld errors", nerrors);
+    if (ntransmitted) {
+#ifdef USE_IDN
+        setlocale(LC_ALL, "C");
+#endif
+        printf(", %g%% packet loss",
+                (float) ((((long long)(ntransmitted - nreceived)) * 100.0) /
+                        ntransmitted));
+        printf(", time %ldms", 1000*tv.tv_sec+(tv.tv_usec+500)/1000);
+    }
+    putchar('\n');
+#endif
+
+    if(a_ping_handle->ping_common.nreceived && a_ping_handle->ping_common.timing) {
+
+        a_ping_handle->ping_common.tsum /= a_ping_handle->ping_common.nreceived + a_ping_handle->ping_common.nrepeats;
+        a_ping_handle->ping_common.tsum2 /= a_ping_handle->ping_common.nreceived + a_ping_handle->ping_common.nrepeats;
+#ifdef PING_DBG
+        long tmdev;
+        tmdev = llsqrt(tsum2 - tsum * tsum);
+        printf("rtt min/avg/max/mdev = %ld.%03ld/%lu.%03ld/%ld.%03ld/%ld.%03ld ms",
+                (long)tmin/1000, (long)tmin%1000,
+                (unsigned long)(tsum/1000), (long)(tsum%1000),
+                (long)tmax/1000, (long)tmax%1000,
+                (long)tmdev/1000, (long)tmdev%1000
+        );
+        comma = ", ";
+#endif
+    }
+#ifdef PING_DBG
+    if (pipesize > 1) {
+        printf("%spipe %d", comma, pipesize);
+        comma = ", ";
+    }
+    if (nreceived && (!interval || (a_ping_handle->ping_common.options&(F_FLOOD|F_ADAPTIVE))) && ntransmitted > 1) {
+        int ipg = (1000000*(long long)tv.tv_sec+tv.tv_usec)/(ntransmitted-1);
+        printf("%sipg/ewma %d.%03d/%d.%03d ms",
+                comma, ipg/1000, ipg%1000, rtt/8000, (rtt/8)%1000);
+    }
+    putchar('\n');
+    //exit(!nreceived || (deadline && nreceived < npackets));
+#endif
+}
+
+void status(ping_handle_t *a_ping_handle)
+{
+    int loss = 0;
+    long tavg = 0;
+
+    status_snapshot = 0;
+
+    if(a_ping_handle->ping_common.ntransmitted)
+        loss = (((long long) (a_ping_handle->ping_common.ntransmitted - a_ping_handle->ping_common.nreceived)) * 100) / a_ping_handle->ping_common.ntransmitted;
+
+    fprintf(stderr, "\r%ld/%ld packets, %d%% loss", a_ping_handle->ping_common.nreceived, a_ping_handle->ping_common.ntransmitted, loss);
+
+    if(a_ping_handle->ping_common.nreceived && a_ping_handle->ping_common.timing) {
+        tavg = a_ping_handle->ping_common.tsum / (a_ping_handle->ping_common.nreceived + a_ping_handle->ping_common.nrepeats);
+
+        fprintf(stderr, ", min/avg/ewma/max = %ld.%03ld/%lu.%03ld/%d.%03d/%ld.%03ld ms",
+                (long) a_ping_handle->ping_common.tmin / 1000, (long) a_ping_handle->ping_common.tmin % 1000,
+                tavg / 1000, tavg % 1000,
+                a_ping_handle->ping_common.rtt / 8000, (a_ping_handle->ping_common.rtt / 8) % 1000,
+                (long) a_ping_handle->ping_common.tmax / 1000, (long) a_ping_handle->ping_common.tmax % 1000
+                        );
+    }
+    fprintf(stderr, "\n");
+}
+
+inline int is_ours(ping_handle_t *a_ping_handle, socket_st *sock, uint16_t id) {
+    return sock->socktype == SOCK_DGRAM || id == a_ping_handle->ping_common.ident;
+}
diff --git a/libdap-chain-net/iputils/tracepath.c b/libdap-chain-net/iputils/tracepath.c
new file mode 100755
index 0000000000000000000000000000000000000000..2dce76420ca7396496ba18068819f85011b0a0a7
--- /dev/null
+++ b/libdap-chain-net/iputils/tracepath.c
@@ -0,0 +1,783 @@
+/*
+ * tracepath.c
+ *
+ *    This program is free software; you can redistribute it and/or
+ *    modify it under the terms of the GNU General Public License
+ *    as published by the Free Software Foundation; either version
+ *    2 of the License, or (at your option) any later version.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ */
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <limits.h>
+#include <time.h>
+#include <linux/errqueue.h>
+#include <linux/icmp.h>
+#include <linux/icmpv6.h>
+#include <linux/types.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <resolv.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/uio.h>
+#include <unistd.h>
+//#include <glib.h>
+
+#include "iputils.h"
+
+#ifdef USE_IDN
+# include <locale.h>
+# ifndef AI_IDN
+#  define AI_IDN 0x0040
+# endif
+# ifndef NI_IDN
+#  define NI_IDN 32
+# endif
+# define getnameinfo_flags  NI_IDN
+#else
+# define getnameinfo_flags  0
+#endif
+
+#ifndef SOL_IPV6
+# define SOL_IPV6 IPPROTO_IPV6
+#endif
+
+#ifndef IP_PMTUDISC_DO
+# define IP_PMTUDISC_DO   3
+#endif
+#ifndef IPV6_PMTUDISC_DO
+# define IPV6_PMTUDISC_DO 3
+#endif
+
+enum {
+    MAX_PROBES = 10,
+
+    MAX_HOPS_DEFAULT = 30,
+    MAX_HOPS_LIMIT = 255,
+
+    HOST_COLUMN_SIZE = 52,
+
+    HIS_ARRAY_SIZE = 64,
+
+    DEFAULT_OVERHEAD_IPV4 = 28,
+    DEFAULT_OVERHEAD_IPV6 = 48,
+
+    DEFAULT_MTU_IPV4 = 65535,
+    DEFAULT_MTU_IPV6 = 128000,
+
+    DEFAULT_BASEPORT = 44444,
+
+    ANCILLARY_DATA_LEN = 512,
+};
+
+struct hhistory {
+    int hops;
+    struct timeval sendtime;
+    struct timeval deltatime;
+    int hop;
+    char *host_namea;
+    char *host_nameb;
+};
+
+struct probehdr {
+    uint32_t ttl;
+    struct timeval tv;
+};
+
+struct run_state {
+    struct hhistory his[HIS_ARRAY_SIZE];
+    int hisptr;
+    struct sockaddr_storage target;
+    struct addrinfo *ai;
+    int socket_fd;
+    socklen_t targetlen;
+    uint16_t base_port;
+    uint8_t ttl;
+    int max_hops;
+    int overhead;
+    int mtu;
+    void *pktbuf;
+    int hops_to;
+    int hops_from;
+    unsigned int
+    no_resolve :1,
+            show_both :1,
+            mapped :1;
+};
+
+/*
+ * All includes, definitions, struct declarations, and global variables are
+ * above.  After this comment all you can find is functions.
+ */
+
+static void data_wait(struct run_state const * const ctl)
+{
+    fd_set fds;
+    struct timeval tv = {
+        .tv_sec = 1,
+        .tv_usec = 0
+    };
+
+    FD_ZERO(&fds);
+    FD_SET(ctl->socket_fd, &fds);
+    select(ctl->socket_fd + 1, &fds, NULL, NULL, &tv);
+}
+
+static void print_host(struct run_state const * const ctl, char const * const a,
+        char const * const b)
+{
+    int plen;
+
+    plen = log_printf("%s", a);
+    if(ctl->show_both)
+        plen += log_printf(" (%s)", b);
+    if(plen >= HOST_COLUMN_SIZE)
+        plen = HOST_COLUMN_SIZE - 1;
+    log_printf("%*s", HOST_COLUMN_SIZE - plen, "");
+}
+
+static int recverr(struct run_state * const ctl)
+{
+    ssize_t recv_size;
+    struct probehdr rcvbuf;
+    char cbuf[ANCILLARY_DATA_LEN];
+    struct cmsghdr *cmsg;
+    struct sock_extended_err *e;
+    struct sockaddr_storage addr;
+    struct timeval tv;
+    struct timeval *rettv;
+    struct timeval *deltatv;
+    int slot = 0;
+    int rethops;
+    int sndhops;
+    int progress = -1;
+    int broken_router;
+    char hnamebuf[NI_MAXHOST] = "";
+    struct iovec iov = {
+        .iov_base = &rcvbuf,
+        .iov_len = sizeof(rcvbuf)
+    };
+    struct msghdr msg;
+    const struct msghdr reset = {
+        .msg_name = (uint8_t *) &addr,
+        .msg_namelen = sizeof(addr),
+        .msg_iov = &iov,
+        .msg_iovlen = 1,
+        .msg_control = cbuf,
+        .msg_controllen = sizeof(cbuf),
+        0
+    };
+
+    restart:
+    memset(&rcvbuf, -1, sizeof(rcvbuf));
+    msg = reset;
+
+    gettimeofday(&tv, NULL);
+    recv_size = recvmsg(ctl->socket_fd, &msg, MSG_ERRQUEUE);
+    if(recv_size < 0) {
+        if(errno == EAGAIN)
+            return progress;
+        goto restart;
+    }
+
+    progress = ctl->mtu;
+
+    rethops = -1;
+    sndhops = -1;
+    e = NULL;
+    rettv = NULL;
+    deltatv = NULL;
+    broken_router = 0;
+
+    slot = -ctl->base_port;
+    switch (ctl->ai->ai_family) {
+    case AF_INET6:
+        slot += ntohs(((struct sockaddr_in6 *) &addr)->sin6_port);
+        break;
+    case AF_INET:
+        slot += ntohs(((struct sockaddr_in *) &addr)->sin_port);
+        break;
+    }
+
+    if(slot >= 0 && slot < (HIS_ARRAY_SIZE - 1) && ctl->his[slot].hops) {
+        sndhops = ctl->his[slot].hops;
+        rettv = &ctl->his[slot].sendtime;
+        deltatv = &ctl->his[slot].deltatime;
+        ctl->his[slot].hop = sndhops;
+        ctl->his[slot].hops = 0;
+    }
+    if(recv_size == sizeof(rcvbuf)) {
+        if(rcvbuf.ttl == 0 || rcvbuf.tv.tv_sec == 0)
+            broken_router = 1;
+        else {
+            sndhops = rcvbuf.ttl;
+            rettv = &rcvbuf.tv;
+        }
+    }
+
+    for(cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+        switch (cmsg->cmsg_level) {
+        case SOL_IPV6:
+            switch (cmsg->cmsg_type) {
+            case IPV6_RECVERR:
+                e = (struct sock_extended_err *) CMSG_DATA(cmsg);
+                break;
+            case IPV6_HOPLIMIT:
+                #ifdef IPV6_2292HOPLIMIT
+            case IPV6_2292HOPLIMIT:
+                #endif
+                memcpy(&rethops, CMSG_DATA(cmsg), sizeof(rethops));
+                break;
+            default:
+                log_printf("cmsg6:%d\n ", cmsg->cmsg_type);
+            }
+            break;
+        case SOL_IP:
+            switch (cmsg->cmsg_type) {
+            case IP_RECVERR:
+                e = (struct sock_extended_err *) CMSG_DATA(cmsg);
+                break;
+            case IP_TTL:
+                rethops = *(uint8_t *) CMSG_DATA(cmsg);
+                break;
+            default:
+                log_printf("cmsg4:%d\n ", cmsg->cmsg_type);
+            }
+        }
+    }
+    if(e == NULL) {
+        log_printf("no info\n");
+        return 0;
+    }
+    if(e->ee_origin == SO_EE_ORIGIN_LOCAL)
+        log_printf("%2d?: %-32s ", ctl->ttl, "[LOCALHOST]");
+    else if(e->ee_origin == SO_EE_ORIGIN_ICMP6 ||
+            e->ee_origin == SO_EE_ORIGIN_ICMP) {
+        char abuf[NI_MAXHOST];
+        struct sockaddr *sa = (struct sockaddr *) (e + 1);
+        socklen_t salen;
+
+        if(sndhops > 0)
+            log_printf("%2d:  ", sndhops);
+        else
+            log_printf("%2d?: ", ctl->ttl);
+
+        switch (sa->sa_family) {
+        case AF_INET6:
+            salen = sizeof(struct sockaddr_in6);
+            break;
+        case AF_INET:
+            salen = sizeof(struct sockaddr_in);
+            break;
+        default:
+            salen = 0;
+        }
+
+        if(ctl->no_resolve || ctl->show_both)
+                {
+            if(getnameinfo(sa, salen, abuf, sizeof(abuf), NULL, 0,
+            NI_NUMERICHOST))
+                strcpy(abuf, "???");
+            ctl->his[slot].host_namea = strdup(abuf);
+        } else
+            abuf[0] = 0;
+
+        if(!ctl->no_resolve || ctl->show_both)
+                {
+            fflush(stdout);
+            if(getnameinfo(sa, salen, hnamebuf, sizeof hnamebuf, NULL, 0,
+            getnameinfo_flags))
+                strcpy(hnamebuf, "???");
+            ctl->his[slot].host_nameb = strdup(hnamebuf);
+        } else
+            hnamebuf[0] = 0;
+
+        if(ctl->no_resolve) {
+            print_host(ctl, abuf, hnamebuf);
+        }
+        else {
+            print_host(ctl, hnamebuf, abuf);
+        }
+    }
+
+    if(rettv) {
+        struct timeval res;
+
+        timersub(&tv, rettv, &res);
+        if(deltatv)
+            memcpy(deltatv, &res, sizeof(struct timeval));
+        log_printf("%3ld.%03ldms ", res.tv_sec * 1000 + res.tv_usec / 1000, res.tv_usec % 1000);
+        if(broken_router)
+            log_printf("(This broken router returned corrupted payload) ");
+    }
+
+    if(rethops <= 64)
+        rethops = 65 - rethops;
+    else if(rethops <= 128)
+        rethops = 129 - rethops;
+    else
+        rethops = 256 - rethops;
+
+    //log_printf("ERROR=%d ", e->ee_errno);
+    switch (e->ee_errno) {
+    case ETIMEDOUT:
+        log_printf("\n");
+        break;
+    case EMSGSIZE:
+        log_printf("pmtu %d\n", e->ee_info);
+        ctl->mtu = e->ee_info;
+        progress = ctl->mtu;
+        break;
+    case ECONNREFUSED:
+        log_printf("reached\n");
+        ctl->hops_to = sndhops < 0 ? ctl->ttl : sndhops;
+        ctl->hops_from = rethops;
+        return 0;
+    case EPROTO:
+        log_printf("!P\n");
+        return 0;
+    case EHOSTUNREACH:
+        if((e->ee_origin == SO_EE_ORIGIN_ICMP &&
+                e->ee_type == ICMP_TIME_EXCEEDED &&
+                e->ee_code == ICMP_EXC_TTL) ||
+                (e->ee_origin == SO_EE_ORIGIN_ICMP6 &&
+                        e->ee_type == ICMPV6_TIME_EXCEED &&
+                        e->ee_code == ICMPV6_EXC_HOPLIMIT)) {
+            if(rethops >= 0) {
+                if(sndhops >= 0 && rethops != sndhops)
+                    log_printf("asymm %2d ", rethops);
+                else if(sndhops < 0 && rethops != ctl->ttl)
+                    log_printf("asymm %2d ", rethops);
+            }
+            //log_printf("hops=->%2d, <-%2d  ", sndhops, rethops);
+            log_printf("\n");
+            break;
+        }
+        printf("!H\n");
+        return 0;
+    case ENETUNREACH:
+        log_printf("!N\n");
+        return 0;
+    case EACCES:
+        log_printf("!A\n");
+        return 0;
+    default:
+        printf("\n");
+        errno = e->ee_errno;
+        perror("NET ERROR");
+        return 0;
+    }
+    goto restart;
+    return 0;
+}
+
+static int probe_ttl(struct run_state * const ctl)
+{
+    int i;
+    struct probehdr *hdr = ctl->pktbuf;
+
+    memset(ctl->pktbuf, 0, ctl->mtu);
+    restart:
+    for(i = 0; i < MAX_PROBES; i++) {
+        int res;
+
+        hdr->ttl = ctl->ttl;
+        switch (ctl->ai->ai_family) {
+        case AF_INET6:
+            ((struct sockaddr_in6 *) &ctl->target)->sin6_port =
+                    htons(ctl->base_port + ctl->hisptr);
+            break;
+        case AF_INET:
+            ((struct sockaddr_in *) &ctl->target)->sin_port =
+                    htons(ctl->base_port + ctl->hisptr);
+            break;
+        }
+        gettimeofday(&hdr->tv, NULL);
+        ctl->his[ctl->hisptr].hops = ctl->ttl;
+        ctl->his[ctl->hisptr].sendtime = hdr->tv;
+        if(sendto(ctl->socket_fd, ctl->pktbuf, ctl->mtu - ctl->overhead, 0,
+                (struct sockaddr *) &ctl->target, ctl->targetlen) > 0)
+            break;
+        res = recverr(ctl);
+        ctl->his[ctl->hisptr].hops = 0;
+        if(res == 0)
+            return 0;
+        if(res > 0)
+            goto restart;
+    }
+    ctl->hisptr = (ctl->hisptr + 1) & (HIS_ARRAY_SIZE - 1);
+
+    if(i < MAX_PROBES) {
+        data_wait(ctl);
+        if(recv(ctl->socket_fd, ctl->pktbuf, ctl->mtu, MSG_DONTWAIT) > 0) {
+            log_printf("%2d?: reply received 8)\n", ctl->ttl);
+            return 0;
+        }
+        return recverr(ctl);
+    }
+
+    log_printf("%2d:  send failed\n", ctl->ttl);
+    return 0;
+}
+
+static void usage(void)
+{
+    fprintf(stderr,
+            "\nUsage\n"
+                    "  tracepath [options] <destination>\n"
+                    "\nOptions:\n"
+                    "  -4             use IPv4\n"
+                    "  -6             use IPv6\n"
+                    "  -b             print both name and ip\n"
+                    "  -l <length>    use packet <length>\n"
+                    "  -m <hops>      use maximum <hops>\n"
+                    "  -n             no dns name resolution\n"
+                    "  -p <port>      use destination <port>\n"
+                    "  -V             print version and exit\n"
+                    "  <destination>  dns name or ip address\n"
+                    "\nFor more details see tracepath(8).\n");
+    exit(-1);
+}
+
+int tracepath_main(int argc, char **argv, struct run_state *ctl)
+{
+    int ret = -1;
+    struct addrinfo hints = {
+        .ai_family = AF_UNSPEC,
+        .ai_socktype = SOCK_DGRAM,
+        .ai_protocol = IPPROTO_UDP,
+#ifdef USE_IDN
+            .ai_flags = AI_IDN | AI_CANONNAME,
+#endif
+        };
+    struct addrinfo *result;
+    int ch;
+    int status;
+    int on;
+    char *p;
+    char pbuf[NI_MAXSERV];
+
+#ifdef USE_IDN
+    setlocale(LC_ALL, "");
+#endif
+
+    /* Support being called using `tracepath4` or `tracepath6` symlinks */
+    if(argv[0][strlen(argv[0]) - 1] == '4')
+        hints.ai_family = AF_INET;
+    else if(argv[0][strlen(argv[0]) - 1] == '6')
+        hints.ai_family = AF_INET6;
+
+    while((ch = getopt(argc, argv, "46nbh?l:m:p:V")) != EOF) {
+        switch (ch) {
+        case '4':
+            if(hints.ai_family == AF_INET6) {
+                fprintf(stderr,
+                        "tracepath: Only one -4 or -6 option may be specified\n");
+                return -1; //exit(2);
+            }
+            hints.ai_family = AF_INET;
+            break;
+        case '6':
+            if(hints.ai_family == AF_INET) {
+                fprintf(stderr,
+                        "tracepath: Only one -4 or -6 option may be specified\n");
+                return -1; //exit(2);
+            }
+            hints.ai_family = AF_INET6;
+            break;
+        case 'n':
+            ctl->no_resolve = 1;
+            break;
+        case 'b':
+            ctl->show_both = 1;
+            break;
+        case 'l':
+            if((ctl->mtu = atoi(optarg)) <= ctl->overhead) {
+                fprintf(stderr,
+                        "Error: pktlen must be > %d and <= %d.\n",
+                        ctl->overhead, INT_MAX);
+                return -1; //exit(1);
+            }
+            break;
+        case 'm':
+            ctl->max_hops = atoi(optarg);
+            if(ctl->max_hops < 0 || ctl->max_hops > MAX_HOPS_LIMIT) {
+                fprintf(stderr,
+                        "Error: max hops must be 0 .. %d (inclusive).\n",
+                        MAX_HOPS_LIMIT);
+                return -1; //exit(1);
+            }
+            break;
+        case 'p':
+            ctl->base_port = atoi(optarg);
+            break;
+        case 'V':
+            printf(IPUTILS_VERSION("tracepath"));
+            return 0;
+        default:
+            usage();
+        }
+    }
+
+    argc -= optind;
+    argv += optind;
+    optind = 0;
+
+    if(argc != 1)
+        usage();
+
+    /* Backward compatibility */
+    if(!ctl->base_port) {
+        p = strchr(argv[0], '/');
+        if(p) {
+            *p = 0;
+            ctl->base_port = atoi(p + 1);
+        } else
+            ctl->base_port = DEFAULT_BASEPORT;
+    }
+    sprintf(pbuf, "%u", ctl->base_port);
+
+    status = getaddrinfo(argv[0], pbuf, &hints, &result);
+    if(status) {
+        fprintf(stderr, "tracepath: %s: %s\n", argv[0],
+                gai_strerror(status));
+        return -EADDRNOTAVAIL;//exit(1);
+    }
+
+    for(ctl->ai = result; ctl->ai; ctl->ai = ctl->ai->ai_next) {
+        if(ctl->ai->ai_family != AF_INET6 && ctl->ai->ai_family != AF_INET)
+            continue;
+        ctl->socket_fd = socket(ctl->ai->ai_family, ctl->ai->ai_socktype, ctl->ai->ai_protocol);
+        if(ctl->socket_fd < 0)
+            continue;
+        memcpy(&ctl->target, ctl->ai->ai_addr, ctl->ai->ai_addrlen);
+        ctl->targetlen = ctl->ai->ai_addrlen;
+        break;
+    }
+    if(ctl->socket_fd < 0) {
+        perror("socket/connect");
+        return -ESOCKTNOSUPPORT;//exit(1);
+    }
+
+    switch (ctl->ai->ai_family) {
+    case AF_INET6:
+        ctl->overhead = DEFAULT_OVERHEAD_IPV6;
+        if(!ctl->mtu)
+            ctl->mtu = DEFAULT_MTU_IPV6;
+        if(ctl->mtu <= ctl->overhead)
+            goto pktlen_error;
+
+        on = IPV6_PMTUDISC_DO;
+        if(setsockopt(ctl->socket_fd, SOL_IPV6, IPV6_MTU_DISCOVER, &on, sizeof(on)) &&
+                (on = IPV6_PMTUDISC_DO, setsockopt(ctl->socket_fd, SOL_IPV6,
+                IPV6_MTU_DISCOVER, &on, sizeof(on)))) {
+            perror("IPV6_MTU_DISCOVER");
+            return -2;//exit(1);
+        }
+        on = 1;
+        if(setsockopt(ctl->socket_fd, SOL_IPV6, IPV6_RECVERR, &on, sizeof(on))) {
+            perror("IPV6_RECVERR");
+            return -3;//exit(1);
+        }
+        if(setsockopt(ctl->socket_fd, SOL_IPV6, IPV6_HOPLIMIT, &on, sizeof(on))
+                #ifdef IPV6_RECVHOPLIMIT
+                && setsockopt(ctl->socket_fd, SOL_IPV6, IPV6_2292HOPLIMIT, &on, sizeof(on))
+                        #endif
+                        ) {
+            perror("IPV6_HOPLIMIT");
+            return -4;//exit(1);
+        }
+        if(!IN6_IS_ADDR_V4MAPPED(&(((struct sockaddr_in6 * )&ctl->target)->sin6_addr)))
+            break;
+        ctl->mapped = 1;
+        /*FALLTHROUGH*/
+    case AF_INET:
+        ctl->overhead = DEFAULT_OVERHEAD_IPV4;
+        if(!ctl->mtu)
+            ctl->mtu = DEFAULT_MTU_IPV4;
+        if(ctl->mtu <= ctl->overhead)
+            goto pktlen_error;
+
+        on = IP_PMTUDISC_DO;
+        if(setsockopt(ctl->socket_fd, SOL_IP, IP_MTU_DISCOVER, &on, sizeof(on))) {
+            perror("IP_MTU_DISCOVER");
+            return -5;//exit(1);
+        }
+        on = 1;
+        if(setsockopt(ctl->socket_fd, SOL_IP, IP_RECVERR, &on, sizeof(on))) {
+            perror("IP_RECVERR");
+            return -6;//exit(1);
+        }
+        if(setsockopt(ctl->socket_fd, SOL_IP, IP_RECVTTL, &on, sizeof(on))) {
+            perror("IP_RECVTTL");
+            return -7;//exit(1);
+        }
+    }
+
+    ctl->pktbuf = malloc(ctl->mtu);
+    if(!ctl->pktbuf) {
+        perror("malloc");
+        return -8; //exit(1);
+    }
+
+//    struct sockaddr sa;
+//    int salen6 = sizeof(struct sockaddr_in6);
+//    int salen = sizeof(struct sockaddr_in);
+    char target_ip[NI_MAXHOST];
+    char *target_name = argv[0];
+    memset(&target_ip, 0, sizeof(target_ip));
+    getnameinfo((struct sockaddr *) &ctl->target, ctl->targetlen, target_ip, sizeof(target_ip), NULL, 0,
+    NI_NUMERICHOST);
+    log_printf("START target_name=%s target_ip=%s\n", target_name, target_ip);
+
+    for(ctl->ttl = 1; ctl->ttl <= ctl->max_hops; ctl->ttl++) {
+        int res;
+        int i;
+
+        on = ctl->ttl;
+        switch (ctl->ai->ai_family) {
+        case AF_INET6:
+            if(setsockopt(ctl->socket_fd, SOL_IPV6, IPV6_UNICAST_HOPS, &on, sizeof(on))) {
+                perror("IPV6_UNICAST_HOPS");
+                return -9; //exit(1);
+            }
+            if(!ctl->mapped)
+                break;
+            /*FALLTHROUGH*/
+        case AF_INET:
+            if(setsockopt(ctl->socket_fd, SOL_IP, IP_TTL, &on, sizeof(on))) {
+                perror("IP_TTL");
+                return -10; //exit(1);
+            }
+        }
+
+        restart:
+        for(i = 0; i < 2; i++) {
+            int old_mtu;
+
+            old_mtu = ctl->mtu;
+            res = probe_ttl(ctl);
+            if(ctl->mtu != old_mtu)
+                goto restart;
+            if(res == 0)
+                goto done;
+            if(res > 0)
+                break;
+        }
+        // if already find need name, make ret>=0 and break
+        {
+            int i = ctl->hisptr - 1;
+            //for(int i = 0; i < MAX_HOPS_DEFAULT; i++)
+            {
+                char *host_namea = (ctl->his[i].host_namea);
+                char *host_nameb = (ctl->his[i].host_nameb);
+                if(host_namea) {
+                    if(!strcmp(host_namea, target_name) || !strcmp(host_namea, target_ip)) {
+                        ctl->ttl = ctl->max_hops;
+                        ret = i;
+                        break;
+                    }
+                }
+                if(host_nameb) {
+                    if(!strcmp(host_nameb, target_name) || !strcmp(host_nameb, target_ip)) {
+                        ctl->ttl = ctl->max_hops;
+                        ret = i;
+                        break;
+                    }
+                }
+            }
+        }
+        if(res < 0)
+            log_printf("%2d:  no reply\n", ctl->ttl);
+    }
+    log_printf("     Too many hops: pmtu %d\n", ctl->mtu);
+
+    done:
+    freeaddrinfo(result);
+
+    log_printf("     Resume: pmtu %d ", ctl->mtu);
+    if(ctl->hops_to >= 0)
+            {
+        ret = ctl->hisptr - 1;
+        log_printf("hops %d ", ctl->hops_to);
+    }
+    if(ctl->hops_from >= 0)
+        log_printf("back %d ", ctl->hops_from);
+    log_printf("\n");
+    return ret; //exit(0);
+
+    pktlen_error:
+    fprintf(stderr, "Error: pktlen must be > %d and <= %d\n",
+            ctl->overhead, INT_MAX);
+    return -1; //exit(1);
+}
+
+/**
+ * Tracepath host
+ *
+ * @addr[in] host name or IP address
+ * @hops[out] hops count
+ * @time_usec[out] latency in microseconds
+ * @return 0 Ok, -1 error
+ */
+int tracepath_util(const char *addr, int *hops, int *time_usec)
+{
+    int type = 4; // 4 or 6
+    int total_hops = 0;
+    long int total_time_usec = 0; // latency in microseconds
+    int argc = 4;
+    const char *argv[argc];
+    if(type != 4)
+        argv[0] = "tracepath6";
+    else
+        argv[0] = "tracepath4";
+    argv[1] = "-b"; // print both name and ip
+    argv[2] = "-m 26"; // -n -m 16
+    argv[3] = addr;
+    struct run_state ctl = {
+        .max_hops = MAX_HOPS_DEFAULT,
+        .hops_to = -1,
+        .hops_from = -1,
+        0
+    };
+    int ret = tracepath_main(argc, (char**) argv, &ctl);
+    for(int i = 0; i < MAX_HOPS_DEFAULT; i++) {
+        free(ctl.his[i].host_namea);
+        free(ctl.his[i].host_nameb);
+    }
+    if(ret >= 0)
+            {
+        struct timeval *deltatime = &(ctl.his[ret].deltatime);
+        total_hops = ctl.his[ret].hop;
+        total_time_usec = deltatime->tv_sec * 1000000 + deltatime->tv_usec;
+    }
+    else
+        for(int i = 0; i < MAX_HOPS_DEFAULT; i++) {
+            struct timeval *deltatime = &(ctl.his[i].deltatime);
+            if(ctl.his[i].hop) { //if(!ctl.his[i].hops && (deltatime->tv_sec > 0 || deltatime->tv_usec > 0)) {
+                total_hops = ctl.his[i].hop;
+                total_time_usec = deltatime->tv_sec * 1000000 + deltatime->tv_usec;
+            }
+            /*if(ctl.his[i].hop > 0)
+             {
+             char *host_name = (ctl.his[i].host_name) ? ctl.his[i].host_name : "-";
+             printf("%d %d: %s %ld\n", ctl.his[i].hops, ctl.his[i].hop, host_name,
+             deltatime->tv_sec * 1000000 + deltatime->tv_usec);
+             }*/
+        }
+    if(hops) {
+        *hops = total_hops;
+    }
+    if(time_usec) {
+        *time_usec = total_time_usec;
+    }
+    return (ret >= 0) ? 0 : ret;
+
+}
+
diff --git a/libdap-chain-net/iputils/traceroute/as_lookups.c b/libdap-chain-net/iputils/traceroute/as_lookups.c
new file mode 100644
index 0000000000000000000000000000000000000000..873180375802bce154e491966ea68f81eab98522
--- /dev/null
+++ b/libdap-chain-net/iputils/traceroute/as_lookups.c
@@ -0,0 +1,128 @@
+/*
+    Copyright (c)  2006, 2007		Dmitry Butskoy
+					<buc@citadel.stu.neva.ru>
+    License:  GPL v2 or any later
+
+    See COPYING for the status of this software.
+*/
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+
+#include "traceroute.h"
+
+
+#define DEF_RADB_SERVER		"whois.radb.net"
+#define DEF_RADB_SERVICE	"nicname"
+
+
+static sockaddr_any ra_addr = {{ 0, }, };
+static char ra_buf[512] = { 0, };
+
+
+const char *get_as_path (const char *query) {
+	int sk, n;
+	FILE *fp;
+	char buf[1024];
+	int prefix = 0, best_prefix = 0;
+	char *rb, *re = &ra_buf[sizeof (ra_buf) / sizeof (*ra_buf) - 1];
+
+
+	if (!ra_addr.sa.sa_family) {
+	    const char *server, *service;
+	    struct addrinfo *res;
+	    int ret;
+
+	    server = getenv ("RA_SERVER");
+	    if (!server)  server = DEF_RADB_SERVER;
+
+	    service = getenv ("RA_SERVICE");
+	    if (!service)  service = DEF_RADB_SERVICE;
+
+
+	    ret = getaddrinfo (server, service, NULL, &res);
+	    if (ret) {
+		fprintf (stderr, "%s/%s: %s\n", server, service,
+						    gai_strerror(ret));
+		exit (2);
+	    }	
+
+	    memcpy (&ra_addr, res->ai_addr, res->ai_addrlen);
+
+	    freeaddrinfo (res);
+	}
+
+
+	sk = socket (ra_addr.sa.sa_family, SOCK_STREAM, 0);
+	if (sk < 0)  error ("socket");
+
+	if (connect (sk, &ra_addr.sa, sizeof (ra_addr)) < 0)
+		goto  err_sk;
+
+	n = snprintf (buf, sizeof (buf), "%s\r\n", query);
+	if (n >= (int)sizeof (buf))  goto err_sk;
+
+	if (write (sk, buf, n) < n)
+		goto err_sk;
+
+	fp = fdopen (sk, "r");
+	if (!fp)  goto err_sk;
+
+
+	strcpy (ra_buf, "*");
+	rb = ra_buf;
+
+	while (fgets (buf, sizeof (buf), fp) != NULL) {
+
+	    if (!strncmp (buf, "route:", sizeof ("route:") - 1) ||
+		!strncmp (buf, "route6:", sizeof ("route6:") - 1)
+	    ) {
+		char *p = strchr (buf, '/');
+
+		if (p)  prefix = strtoul (++p, NULL, 10);
+		else  prefix = 0;	/*  Hmmm...  */
+
+	    }
+	    else if (!strncmp (buf, "origin:", sizeof ("origin:") -1)) {
+		char *p, *as;
+
+		p = buf + (sizeof ("origin:") - 1);
+
+		while (isspace (*p))  p++;
+		as = p;
+		while (*p && !isspace (*p))  p++;
+		*p = '\0';
+
+		if (prefix > best_prefix) {
+		    best_prefix = prefix;
+
+		    rb = ra_buf;
+		    while (rb < re && (*rb++ = *as++)) ;
+		}
+		else if (prefix == best_prefix) {
+		    char *q = strstr (ra_buf, as);
+
+		    if (!q || (*(q += strlen (as)) != '\0' && *q != '/')) {
+			if (rb > ra_buf)  rb[-1] = '/';
+			while (rb < re && (*rb++ = *as++)) ;
+		    }
+		}
+		/*  else just ignore it   */
+	    }
+	}
+
+	fclose (fp);
+
+	return ra_buf;
+
+
+err_sk:
+	close (sk);
+	return "!!";
+}
diff --git a/libdap-chain-net/iputils/traceroute/clif.c b/libdap-chain-net/iputils/traceroute/clif.c
new file mode 100644
index 0000000000000000000000000000000000000000..4380e8ecd061e5becee1f296c89a8c8bbf127b51
--- /dev/null
+++ b/libdap-chain-net/iputils/traceroute/clif.c
@@ -0,0 +1,1284 @@
+/*
+ Copyright (c)  2000, 2003		Dmitry Butskoy
+ <buc@citadel.stu.neva.ru>
+ License:  LGPL v2.1 or any later
+
+ See COPYING.LIB for the status of this software.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include "../iputils.h"
+#include "clif.h"
+
+#if 1	/*  Bad idea, anyway...  */
+#define MAX_ARGC_NUMBER	256
+typedef unsigned char _CLIF_index;
+#else
+#define MAX_ARGC_NUMBER	(4096 / 5 + 1)	/*  POSIX ARG_MAX >= 4096 ...  */
+typedef unsigned short _CLIF_index;
+#endif
+
+/*  This is needed for some print info functions.
+ This is ugly for thread-safe (is it really actual on program invoking?),
+ and for several CLIF_parse_cmdline invoking... But foo on this. Yeah...
+ */
+static struct {
+    int argc;
+    char **argv;
+    CLIF_option *option_list;
+    CLIF_argument *argument_list;
+    unsigned int parse_flags;
+} curr = { 0, };
+
+static void err_report(const char *format, ...) {
+    va_list ap;
+
+    if(curr.parse_flags & CLIF_SILENT)
+        return;
+
+    va_start(ap, format);
+
+    vfprintf(stderr, format, ap);
+
+    va_end(ap);
+
+    fprintf(stderr, "\n");
+
+    return;
+}
+
+/*  info generation stuff...   */
+
+#define SHORT_PLUS_MINUS	"+/-"
+#define LONG_PLUS_MINUS		"++/--"
+#define EXCL_DLM		" | "
+
+static char *show_short(const CLIF_option *optn) {
+    static char buf[80];
+    char *p = buf;
+    unsigned int flags = optn->flags | curr.parse_flags;
+
+    if(optn->function_plus) {
+        if(!optn->function)
+            *p++ = '+';
+        else {
+            strcpy(p, SHORT_PLUS_MINUS);
+            p += sizeof(SHORT_PLUS_MINUS) - 1;
+        }
+    } else
+        *p++ = '-';
+
+    *p++ = optn->short_opt[0];
+
+    if(optn->arg_name) {
+        char *endp = buf + sizeof(buf) - sizeof(",...]");
+        const char *s;
+
+        if(!(flags & _CLIF_STRICT_JOIN_ARG))
+            *p++ = ' ';
+        if(flags & CLIF_OPTARG)
+            *p++ = '[';
+
+        s = optn->arg_name;
+        while(*s && p < endp)
+            *p++ = *s++;
+
+        if(flags & CLIF_SEVERAL) {
+            strcpy(p, ",...");
+            p += sizeof(",...") - 1; /*  last '\0' ...  */
+        }
+
+        if(flags & CLIF_OPTARG)
+            *p++ = ']';
+    }
+
+    *p = '\0';
+
+    return buf;
+}
+
+static char *show_long(const CLIF_option *optn) {
+    static char buf[80];
+    char *p = buf;
+    char *endp;
+    const char *s;
+    unsigned int flags = optn->flags | curr.parse_flags;
+
+    if(!(flags & _CLIF_STRICT_KEYWORD)) {
+
+        if(!(flags & _CLIF_STRICT_ONEDASH)) {
+            if(optn->function_plus) {
+                if(!optn->function) {
+                    *p++ = '+';
+                    *p++ = '+';
+                }
+                else {
+                    strcpy(p, LONG_PLUS_MINUS);
+                    p += sizeof(LONG_PLUS_MINUS) - 1;
+                }
+            } else {
+                *p++ = '-';
+                *p++ = '-';
+            }
+
+        } else {
+            if(optn->function_plus) {
+                if(!optn->function)
+                    *p++ = '+';
+                else {
+                    strcpy(p, SHORT_PLUS_MINUS);
+                    p += sizeof(SHORT_PLUS_MINUS) - 1;
+                }
+            } else
+                *p++ = '-';
+        }
+    }
+
+    s = optn->long_opt;
+    endp = buf + sizeof(buf) - sizeof(" [");
+    while(*s && p < endp)
+        *p++ = *s++;
+
+    if(optn->arg_name) {
+
+        if(flags & _CLIF_STRICT_NOEQUAL) {
+            *p++ = ' ';
+            if(flags & CLIF_OPTARG)
+                *p++ = '[';
+        } else {
+            if(flags & CLIF_OPTARG)
+                *p++ = '[';
+            *p++ = '=';
+        }
+
+        s = optn->arg_name;
+        endp = buf + sizeof(buf) - sizeof(",...]");
+        while(*s && p < endp)
+            *p++ = *s++;
+
+        if(flags & CLIF_SEVERAL) {
+            strcpy(p, ",...");
+            p += sizeof(",...") - 1; /*  last '\0' ...  */
+        }
+
+        if(flags & CLIF_OPTARG)
+            *p++ = ']';
+    }
+
+    *p = '\0';
+
+    return buf;
+}
+
+static char *show_excl(const CLIF_option *option_list, int *cnt_p) {
+    static char buf[256];
+    const CLIF_option *optn;
+    char *p = buf;
+    char *endp = buf + sizeof(buf) - sizeof(EXCL_DLM);
+    int excl_cnt = 0;
+
+    *p = '\0';
+    if(cnt_p)
+        *cnt_p = 0;
+    if(!option_list)
+        return buf;
+
+    for(optn = option_list; optn->short_opt || optn->long_opt; optn++) {
+        char *s;
+
+        if(!(optn->flags & CLIF_EXCL))
+            continue;
+
+        if(optn->short_opt)
+            s = show_short(optn);
+        else
+            s = show_long(optn);
+
+        if(excl_cnt > 0) { /*  i.e., second etc...  */
+            strcpy(p, EXCL_DLM);
+            p += sizeof(EXCL_DLM) - 1;
+        }
+
+        while(*s && p < endp)
+            *p++ = *s++;
+
+        excl_cnt++;
+    }
+
+    *p = '\0';
+
+    if(cnt_p)
+        *cnt_p = excl_cnt;
+
+    return buf;
+}
+
+static int is_keyword(const CLIF_option *optn) {
+    unsigned int flags = optn->flags | curr.parse_flags;
+
+    return (flags & _CLIF_STRICT_KEYWORD) != 0;
+}
+
+static void err_bad_opt(const char *arg, char c, int n) {
+    char sym = (*arg == '+') ? '+' : '-';
+
+    if(c)
+        err_report("Bad option `%c%c' (argc %d)", sym, c, n);
+    else {
+        char *p = strchr(arg, '=');
+        const char *type = (*arg == sym) ? "option" : "keyword";
+
+        if(p)
+            err_report("Bad %s `%s' (with arg `%s') (argc %d)",
+                    type, arg, p + 1, n);
+        else
+            err_report("Bad %s `%s' (argc %d)", type, arg, n);
+    }
+}
+
+static void err_bad_arg(const CLIF_option *optn, char c, int n) {
+    CLIF_option tmp = *optn;
+    char ss[80];
+    char *s;
+
+    tmp.arg_name = NULL;
+
+    if(c) {
+        s = show_short(&tmp); /*  always without arg...  */
+        strncpy(ss, s, sizeof(ss));
+        s = show_short(optn);
+    } else {
+        s = show_long(&tmp); /*  always without arg...  */
+        strncpy(ss, s, sizeof(ss));
+        s = show_long(optn);
+    }
+
+    err_report("%s `%s' (argc %d) requires an argument: `%s'",
+            (c || !is_keyword(optn)) ? "Option" : "Keyword", ss, n, s);
+}
+
+static void err_bad_res(const CLIF_option *optn, char c,
+        const char *opt_arg, int n) {
+    CLIF_option tmp = *optn;
+    char *ss;
+    const char *type;
+
+    tmp.arg_name = NULL;
+
+    if(c) {
+        ss = show_short(&tmp);
+        type = "option";
+    } else {
+        ss = show_long(&tmp);
+        type = is_keyword(optn) ? "keyword" : "option";
+    }
+
+    if(optn->arg_name)
+        err_report("Cannot handle `%s' %s with arg `%s' (argc %d)",
+                ss, type, opt_arg, n);
+    else
+        err_report("Cannot handle `%s' %s (argc %d)", ss, type, n);
+}
+
+static void err_bad_excl(const CLIF_option *optn, char c, int n) {
+    CLIF_option tmp = *optn;
+    char *ss;
+    char *excl = show_excl(curr.option_list, 0);
+    /*  Note: show_(short|long)() nested!!! */
+
+    tmp.arg_name = NULL;
+
+    if(c)
+        ss = show_short(&tmp);
+    else
+        ss = show_long(&tmp);
+
+    err_report("%s `%s' (argc %d): Only one of:\n    %s\n"
+            "may be specified.",
+            (c || !is_keyword(optn)) ? "Option" : "Keyword",
+            ss, n, excl);
+}
+
+static CLIF_option *find_long(char *arg, char **arg_p,
+        unsigned int match, unsigned int nomatch) {
+    CLIF_option *optn;
+    CLIF_option *abbrev = NULL;
+    char *abbrev_arg = NULL;
+    int abbrev_found = 0;
+
+    for(optn = curr.option_list;
+            optn->short_opt || optn->long_opt;
+            optn++
+            ) {
+        char *a;
+        const char *o;
+        unsigned int flags;
+
+        if(!optn->long_opt)
+            continue;
+
+        flags = curr.parse_flags | optn->flags;
+        if(flags & nomatch)
+            continue;
+        if(match && !(flags & match))
+            continue; /*  XXX: optimize it */
+
+        for(a = arg, o = optn->long_opt; *o && *a == *o; a++, o++)
+            ;
+
+        if(*a == '\0' ||
+                (*a == '=' && optn->arg_name && !(flags & _CLIF_STRICT_NOEQUAL))
+                ) { /*  looks like end of option...  */
+
+            if(!*o) { /*  explicit match found   */
+                if(*a == '=' && arg_p)
+                    *arg_p = a + 1;
+                return optn;
+            }
+
+            if((flags & CLIF_ABBREV) &&
+                    (a - arg >= CLIF_MIN_ABBREV)
+                    ) {
+                if(!abbrev_found) {
+                    abbrev_found = 1;
+                    abbrev = optn;
+                    if(*a == '=')
+                        abbrev_arg = a + 1;
+                } else
+                    /*  several possibility case...  */
+                    abbrev = NULL;
+            }
+        }
+    }
+
+    if(abbrev) { /*  implicit match found   */
+        if(abbrev_arg && arg_p)
+            *arg_p = abbrev_arg;
+        return abbrev;
+    } else
+        /*  no match found   */
+        return NULL;
+}
+
+static int check_sym(const CLIF_option *optn, char sym) {
+
+    if(sym == '+') {
+        if(!optn->function_plus)
+            return -1;
+    }
+    else if(sym == '-') {
+        if(!optn->function && optn->function_plus)
+            return -1;
+    }
+
+    return 0;
+}
+
+static int call_function(CLIF_option *optn, char *opt_arg, char sym) {
+    int (*function)(CLIF_option *, char *);
+
+    function = (sym == '+') ? optn->function_plus : optn->function;
+
+    if(!function)
+        return 0;
+
+    if(opt_arg && ((optn->flags | curr.parse_flags) & CLIF_SEVERAL)) {
+        char tmp[80];
+        char *t;
+        char *endt = tmp + sizeof(tmp);
+
+        while(*opt_arg) {
+
+            t = tmp;
+            while(t < endt && *opt_arg &&
+                    *opt_arg != ' ' && *opt_arg != '\t' && *opt_arg != ','
+            )
+                *t++ = *opt_arg++;
+
+            if(t >= endt)
+                return -1;
+
+            *t = '\0';
+
+            if(function(optn, tmp) < 0)
+                return -1;
+
+            while(*opt_arg == ' ' || *opt_arg == '\t' || *opt_arg == ',')
+                opt_arg++;
+        }
+
+        return 0;
+    }
+
+    return function(optn, opt_arg);
+}
+
+int CLIF_parse_cmdline (int argc, char *argv[],
+        CLIF_option *option_list,
+        CLIF_argument *argument_list,
+        unsigned int parse_flags){
+int i, j;
+CLIF_option *optn;
+CLIF_argument *argm;
+int num_args = 0;
+int num_argm = 0, strict_beg = 0, strict_end = 0;
+_CLIF_index arg_n[MAX_ARGC_NUMBER];
+unsigned int dirty_flags = 0;
+int dirty_plus = 0;
+int exclusive_cnt = 0;
+int posix = getenv ("POSIXLY_CORRECT") != NULL ||
+(parse_flags & CLIF_POSIX);
+
+curr.argc = argc;
+curr.argv = argv;
+curr.option_list = option_list;
+curr.argument_list = argument_list;
+curr.parse_flags = parse_flags;
+
+if (argc <= 1 && (parse_flags & CLIF_HELP_EMPTY)) {
+    CLIF_current_help ();
+    exit (0);
+}
+
+/*  Scan argument_list for check and some info.  */
+
+if (argument_list) {
+    enum stages {STRICT_BEG, OPTIONAL, STRICT_END};
+    int stage = STRICT_BEG;
+
+    for (argm = argument_list; argm->name; argm++) {
+
+        if (argm->flags & CLIF_STRICT) {
+
+            if (stage == STRICT_BEG) strict_beg++;
+            else if (stage == OPTIONAL) {
+                stage = STRICT_END;
+                strict_end++;
+            }
+            else if (stage == STRICT_END)
+            strict_end++;
+        } else {
+            if (stage == STRICT_BEG) stage = OPTIONAL;
+            else if (stage == STRICT_END) {
+                err_report ("Incorrect argument list set in program "
+                        "source: more than one optional area.");
+                return -1;
+            }
+        }
+
+        num_argm++;
+    }
+}
+
+/*  Scan option_list for some info.  */
+if (option_list) {
+
+    dirty_flags = parse_flags;
+
+    for (optn = option_list;
+            optn->short_opt || optn->long_opt;
+            optn++
+    ) {
+        dirty_flags |= optn->flags;
+        if (optn->function_plus) dirty_plus = 1;
+    }
+}
+
+if (dirty_flags & CLIF_EXCL)
+exclusive_cnt = 1; /*  only one is allowed...  */
+
+/*  Go !   Store arguments, parse options.  */
+
+for (i = 1; i < argc; i++) {
+    char *arg = argv[i];
+    char *opt_arg = NULL;
+    char sym = '-';
+
+    if (!option_list)
+    goto handle_arg;
+
+    if (*arg == '+' && dirty_plus)
+    sym = '+';
+
+    if (*arg != sym) { /*  argument or keyword   */
+
+        if (dirty_flags & CLIF_MAY_KEYWORD) {
+            optn = find_long (arg, &opt_arg, CLIF_MAY_KEYWORD, 0);
+            if (optn) goto long_found;
+        }
+
+        if (num_args == 0 && (parse_flags & CLIF_FIRST_GROUP)) {
+            /*  ugly...  */
+            parse_flags &= ~CLIF_FIRST_GROUP;
+            dirty_flags &= ~CLIF_FIRST_GROUP; /*  to be correct   */
+
+            goto handle_short;
+        }
+
+        /*  else it is an argument   */
+        goto handle_arg;
+
+    }
+    else if (*++arg == sym) { /*  `--' - long option   */
+        arg++;
+
+        if (*arg == sym || /*  `---' - let it be not option... */
+                (parse_flags & (_CLIF_STRICT_KEYWORD|_CLIF_STRICT_ONEDASH))
+        ) {
+            arg -= 2;
+            goto handle_arg; /*  not option anyway  */
+        }
+
+        optn = find_long (arg, &opt_arg, 0,
+                _CLIF_STRICT_KEYWORD | _CLIF_STRICT_ONEDASH);
+        if (optn) goto long_found;
+
+        /*  XXX: May be allow only for `--', not `++' too...  */
+        if (!*arg && sym == '-') { /*  `--' and no empty longoption */
+            option_list = NULL; /*  POSIX way...  */
+            continue;
+        }
+
+        /*  XXX: or treat as an argument sometimes???  */
+        err_bad_opt (argv[i], 0, i);
+        return -1;
+    }
+    else { /*  short option, or several short options...  */
+
+        if (dirty_flags & CLIF_MAY_ONEDASH) {
+            optn = find_long (arg, &opt_arg, CLIF_MAY_ONEDASH, 0);
+            if (optn) goto long_found;
+        }
+
+        if (!*arg) { /*  POSIX say: only "stdout specification"... */
+            arg--;
+            goto handle_arg;
+        }
+
+        goto handle_short;
+    }
+
+    long_found:
+    if (check_sym (optn, sym) < 0) { /*  Oops...  */
+        err_bad_opt (argv[i], 0, i);
+        return -1;
+    }
+
+    if (optn->flags & CLIF_EXCL) {
+        if (!exclusive_cnt) {
+            err_bad_excl (optn, 0, i);
+            return -1;
+        }
+        exclusive_cnt--;
+    }
+
+    if (optn->arg_name && !opt_arg) {
+        unsigned int flags = optn->flags | parse_flags;
+
+        if (++i >= argc ||
+                !(flags & CLIF_MAY_NOEQUAL)
+        ) { /*  missing opt arg   */
+            i--;
+
+            if (!(flags & CLIF_OPTARG)) {
+                err_bad_arg (optn, 0, i);
+                return -1;
+            }
+
+            opt_arg = NULL;
+        } else
+        opt_arg = argv[i];
+
+    }
+
+    if (call_function (optn, opt_arg, sym) < 0) {
+        err_bad_res (optn, 0, opt_arg, i);
+        return -1;
+    }
+
+    if (optn->flags & CLIF_EXIT)
+    exit (0);
+
+    continue;
+
+    handle_arg:
+    if (argument_list) {
+        if (i < MAX_ARGC_NUMBER) /*  XXX: ugly, better report   */
+        arg_n[num_args++] = i;
+    } else {
+        err_report ("`%s' (argc %d): arguments are not allowed",
+                argv[i], i);
+        return -1;
+    }
+
+    /*  POSIX say: No more options after args...  */
+    if (posix) option_list = NULL; /*  geniously...  */
+
+    continue;
+
+    handle_short:
+
+    opt_arg = NULL;
+
+    do {
+
+        for (optn = option_list;
+                optn->short_opt || optn->long_opt;
+                optn++
+        ) {
+            if (optn->short_opt && optn->short_opt[0] == *arg)
+            break;
+        }
+        if (!optn->short_opt ||
+                check_sym (optn, sym) < 0
+        ) {
+            err_bad_opt (argv[i], *arg, i);
+            return -1;
+        }
+
+        if (optn->flags & CLIF_EXCL) {
+            if (!exclusive_cnt) {
+                err_bad_excl (optn, *arg, i);
+                return -1;
+            }
+            exclusive_cnt--;
+        }
+
+        if (optn->arg_name) {
+            unsigned int flags = parse_flags | optn->flags;
+
+            if (arg[1] == '\0') { /*  a last one   */
+
+                /*  POSIX say: an option with arg cannot be grouped. */
+                if (posix && arg != argv[i] && arg[-1] != sym) {
+                    err_bad_arg (optn, *arg, i); /*  good way? */
+                    return -1;
+                }
+
+                if (++i >= argc ||
+                        (flags & _CLIF_STRICT_JOIN_ARG)
+                ) {
+                    i--;
+
+                    if (!(flags & CLIF_OPTARG)) {
+                        err_bad_arg (optn, *arg, i);
+                        return -1;
+                    }
+
+                    opt_arg = NULL;
+                } else
+                opt_arg = argv[i];
+            }
+            else if ((arg == argv[i] || arg[-1] == sym) &&
+                    (flags & CLIF_MAY_JOIN_ARG)
+            ) {
+                opt_arg = ++arg;
+            }
+            else { /*  inside a group...  */
+                if (!(flags & CLIF_OPTARG) ||
+                        (flags & CLIF_MAY_JOIN_ARG)
+                ) {
+                    err_bad_arg (optn, *arg, i);
+                    return -1;
+                }
+
+                opt_arg = NULL;
+            }
+        }
+
+        if (call_function (optn, opt_arg, sym) < 0) {
+            err_bad_res (optn, optn->short_opt[0], opt_arg, i);
+            return -1;
+        }
+
+        if (optn->flags & CLIF_EXIT)
+        exit (0);
+
+    }while (!opt_arg && *++arg);
+
+} /*  for ( ...  )   */
+
+if ((parse_flags & CLIF_STRICT_EXCL) && exclusive_cnt != 0) {
+    err_report ("One of these must be specified:\n    %s\n",
+            show_excl (option_list, 0));
+    return -1;
+}
+
+/*  Now, after *ALL* options, handle arguments, if any.  */
+
+if (num_args < strict_beg + strict_end) {
+    /*  Missing some needed arguments.  */
+
+    if (num_args < strict_beg) argm = argument_list + num_args;
+    else
+    argm = argument_list +
+    ((num_args - strict_beg) + (num_argm - strict_end));
+
+    if (num_args == strict_beg + strict_end - 1)
+    err_report ("Specify \"%s\" missing argument.", argm->name);
+    else
+    err_report ("Specify \"%s\" and other missing arguments.",
+            argm->name);
+    return -1;
+}
+
+if (num_args > 0) {
+    _CLIF_index argm_index[MAX_ARGC_NUMBER];
+
+    /*  assing argm (by index) for each arg...  */
+
+    for (i = 0, j = 0; i < strict_beg; i++, j++)
+    argm_index[i] = j;
+    for (i = num_args - strict_end, j = num_argm - strict_end;
+            i < num_args; i++, j++
+    ) argm_index[i] = j;
+    for (i = strict_beg, j = strict_beg;
+            i < num_args - strict_end && j < num_argm - strict_end;
+            i++
+    ) {
+        argm_index[i] = j;
+        if (!(argument_list[j].flags & CLIF_MORE))
+        j++;
+    }
+
+    if (i < num_args - strict_end) { /*  there are extra args...  */
+        err_report ("Extra arg `%s' (position %d, argc %d)",
+                argv[arg_n[i]], i + 1, arg_n[i]);
+        return -1;
+    }
+
+    if (j < num_argm - strict_end &&
+            !(argument_list[j].flags & CLIF_MORE) &&
+            /*  ...i.e, there are some missing optional args...  */
+            (argument_list[j].flags & CLIF_ACC_PREV)
+    ) {
+        if (j == 0)
+        err_report ("Incorrect argument list set: first arg "
+                "cannot be `accompanied with previous'.");
+        else
+        err_report ("Arg \"%s\" must be specified because "
+                "\"%s\" `%s' is used.", argument_list[j].name,
+                argument_list[j - 1].name, argv[arg_n[i - 1]]);
+        return -1;
+    }
+
+    if (argm_index[--i] == j &&
+            /*  above is true only after OPTIONAL area scan
+             and when `j' is stopped on CLIF_MORE  */
+            ++j < num_argm - strict_end
+            /*  i.e: there is a *last* one (after CLIF_MORE)
+             in the OPTIONAL area  */
+    ) argm_index[i] = j; /*  *last* is better than *more*   */
+
+    /*  ...and work now   */
+
+    for (i = 0; i < num_args; i++) {
+        argm = argument_list + argm_index[i];
+
+        if (argm->function &&
+                argm->function (argm, argv[arg_n[i]], i) < 0
+        ) {
+            err_report ("Cannot handle \"%s\" cmdline arg `%s' "
+                    "on position %d (argc %d)",
+                    argm->name, argv[arg_n[i]], i + 1, arg_n[i]);
+            return -1;
+        }
+    }
+
+    /*  That`s all.  */
+}
+
+return 0;
+}
+
+static void box_output(int start, int left, int width, const char *str,
+        const char *arg_name) {
+    char *p, *endp, *s;
+    int l;
+    char buf[1024];
+    char spacer[128]; /*  assume it is enough   */
+
+    if(left > (int) sizeof(spacer) - 2)
+        left = (int) sizeof(spacer) - 2;
+    if(width > (int) sizeof(buf) - 1)
+        width = (int) sizeof(buf) - 1;
+
+    spacer[0] = '\n';
+    memset(spacer + 1, ' ', left);
+    spacer[left + 1] = '\0';
+
+    l = left - start;
+    if(l > 0) {
+        memset(buf, ' ', l);
+        buf[l] = '\0';
+        fprintf(stderr, "%s", buf);
+    } else
+        fprintf(stderr, "%s", spacer);
+
+    endp = buf + width;
+
+    p = buf;
+
+    while(*str) {
+
+        while(*str && p < endp) {
+
+            if(*str == '%' && arg_name) {
+                if(str[1] == '%') {
+                    *p++ = '%';
+                    str += 2;
+                    continue;
+                }
+                else if(str[1] == 's') {
+                    const char *a = arg_name;
+
+                    while(*a && p < endp)
+                        *p++ = *a++;
+                    str += 2;
+                    continue;
+                }
+            }
+
+            *p++ = *str++;
+        }
+
+        *p = '\0';
+
+        if(p < endp)
+            break;
+
+        while(p > buf && *p != ' ' && *p != '\t')
+            p--;
+        if(p <= buf)
+            return; /*  foo on you   */
+
+        *p = '\0';
+        fprintf(stderr, "%s", buf);
+        fprintf(stderr, "%s", spacer);
+
+        p++;
+        for(s = buf; *p; *s++ = *p++)
+            ;
+        *s = '\0';
+        p = s;
+    }
+
+    fprintf(stderr, "%s", buf);
+
+    return;
+}
+
+#define SHORT_LONG_DLM	"  "
+#define OPT_START_DLM	"  "
+#define OPT_FIELD_WIDTH	30
+
+#define ARG_MARK_STRICT	"+     "
+#define ARG_MARK_GROUP0	"  .   "
+#define ARG_MARK_GROUP	"  '   "
+#define ARG_MARK_OPT	"      "
+#define ARG_FIELD_WIDTH	20
+
+#define SCREEN_WIDTH	80
+
+void CLIF_print_options(const char *header,
+        const CLIF_option *option_list) {
+    const CLIF_option *optn;
+    char *excl;
+    int excl_cnt = 0;
+
+    /*  Print a header string, if present...  */
+    if(header)
+        fprintf(stderr, "%s\n", header);
+
+    if(!option_list)
+        return;
+
+    for(optn = option_list; optn->short_opt || optn->long_opt; optn++) {
+        int len;
+
+        /*  generate and print an option usage   */
+
+        if(optn->short_opt) {
+            if(optn->long_opt)
+                len = fprintf(stderr, OPT_START_DLM "%s"
+                SHORT_LONG_DLM "%s",
+                        show_short(optn), show_long(optn));
+            else
+                len = fprintf(stderr, OPT_START_DLM "%s",
+                        show_short(optn));
+        } else
+            len = fprintf(stderr, OPT_START_DLM "%s", show_long(optn));
+
+        /*  print a help string, if present   */
+
+        if(optn->help_string)
+            box_output(len, OPT_FIELD_WIDTH,
+            SCREEN_WIDTH - OPT_FIELD_WIDTH,
+                    optn->help_string, optn->arg_name);
+
+        fprintf(stderr, "\n"); /*  a last one   */
+    }
+
+    excl = show_excl(option_list, &excl_cnt);
+    if(excl_cnt > 0) {
+
+        if(excl_cnt == 1) {
+            if((curr.parse_flags & CLIF_STRICT_EXCL) &&
+                    curr.option_list == option_list
+                            )
+                fprintf(stderr, "Anyway `%s' must be specified.\n", excl);
+            //else  /*  simple ordinary option, because excl_cnt == 1 ... */
+        } else
+            fprintf(stderr, "Only one of these may be specified:\n"
+                    "    %s\n", excl);
+    }
+
+    return;
+}
+
+void CLIF_print_arguments(const char *header,
+        const CLIF_argument *argument_list) {
+    const CLIF_argument *argm;
+
+    if(!argument_list)
+        return;
+
+    /*  Print a header string, if present...  */
+    if(header)
+        fprintf(stderr, "%s\n", header);
+
+    for(argm = argument_list; argm->name; argm++) {
+        int len;
+
+        if(argm->flags & CLIF_STRICT)
+            len = fprintf(stderr, ARG_MARK_STRICT "%s", argm->name);
+        else if(argm->flags & CLIF_MORE)
+            len = fprintf(stderr, ARG_MARK_OPT "%s ...", argm->name);
+        else if(argm->flags & CLIF_ACC_PREV)
+            len = fprintf(stderr, ARG_MARK_GROUP "%s", argm->name);
+        else if((argm + 1)->name && ((argm + 1)->flags & CLIF_ACC_PREV))
+            len = fprintf(stderr, ARG_MARK_GROUP0 "%s", argm->name);
+        else
+            len = fprintf(stderr, ARG_MARK_OPT "%s", argm->name);
+
+        if(argm->help_string)
+            box_output(len, ARG_FIELD_WIDTH,
+            SCREEN_WIDTH - ARG_FIELD_WIDTH,
+                    argm->help_string, argm->name);
+
+        fprintf(stderr, "\n");
+    }
+
+    return;
+}
+
+void CLIF_print_usage(const char *header, const char *progname,
+        const CLIF_option *option_list,
+        const CLIF_argument *argument_list) {
+
+    if(!progname && curr.argv)
+        progname = curr.argv[0];
+
+    if(!header) {
+        if(progname)
+            fprintf(stderr, "Usage: %s", progname);
+        else
+            fprintf(stderr, "Command line options:");
+    } else {
+        if(progname)
+            fprintf(stderr, "%s\n" OPT_START_DLM "%s", header, progname);
+        else
+            fprintf(stderr, "%s", header);
+    }
+
+    if(option_list) {
+        const CLIF_option *optn;
+        char m_buf[256], p_buf[256], mp_buf[256];
+        char *m = m_buf, *p = p_buf, *mp = mp_buf;
+        char *end_m = m_buf + sizeof(m_buf) - 1;
+        char *end_p = p_buf + sizeof(p_buf) - 1;
+        char *end_mp = mp_buf + sizeof(mp_buf) - 1;
+        char *excl;
+        int excl_cnt = 0;
+
+        /*  first, show exclusive option list, if any...  */
+
+        excl = show_excl(option_list, &excl_cnt);
+        if(excl_cnt > 0) {
+            if((curr.parse_flags & CLIF_STRICT_EXCL) &&
+                    curr.option_list == option_list
+                            ) {
+                if(excl_cnt == 1)
+                    fprintf(stderr, " %s", excl);
+                else
+                    fprintf(stderr, " { %s }", excl);
+            } else
+                fprintf(stderr, " [ %s ]", excl);
+        }
+
+        /*  second, find short options without arguments...  */
+
+        for(optn = option_list;
+                optn->short_opt || optn->long_opt;
+                optn++
+                ) {
+            /*  We don`t exclude CLIF_EXTRA hear:
+             simple one char don`t eat a lot of space...
+             */
+
+            if(!optn->short_opt ||
+                    optn->arg_name ||
+                    (optn->flags & CLIF_EXCL)
+                    )
+                continue;
+
+            if(optn->function_plus) {
+                if(optn->function) {
+                    if(mp < end_mp)
+                        *mp++ = optn->short_opt[0];
+                } else {
+                    if(p < end_p)
+                        *p++ = optn->short_opt[0];
+                }
+            } else {
+                if(m < end_m)
+                    *m++ = optn->short_opt[0];
+            }
+        }
+
+        if(m > (char *) m_buf) {
+            *m = '\0';
+            fprintf(stderr, " [ -%s ]", m_buf);
+        }
+        if(p > (char *) p_buf) {
+            *p = '\0';
+            fprintf(stderr, " [ +%s ]", p_buf);
+        }
+        if(mp > (char *) mp_buf) {
+            *mp = '\0';
+            fprintf(stderr, " [ " SHORT_PLUS_MINUS "%s ]", mp_buf);
+        }
+
+        /*  third, print all another...  */
+
+        for(optn = option_list;
+                optn->short_opt || optn->long_opt;
+                optn++
+                ) {
+            if(optn->flags & CLIF_EXTRA)
+                continue;
+
+            if(optn->flags & CLIF_EXCL)
+                continue; /*  already handled   */
+
+            if(optn->short_opt) {
+                if(optn->arg_name)
+                    fprintf(stderr, " [ %s ]", show_short(optn));
+                //else
+                /*  already handled   */
+            } else
+                fprintf(stderr, " [ %s ]", show_long(optn));
+        }
+    }
+
+    if(argument_list) {
+        const CLIF_argument *argm;
+        int deep = 0;
+
+        for(argm = argument_list; argm->name; argm++) {
+
+            if(argm->flags & CLIF_STRICT) {
+                if(deep > 0) {
+                    fputc(' ', stderr);
+                    while(deep--)
+                        fputc(']', stderr);
+                    deep = 0;
+                }
+
+                fprintf(stderr, " %s", argm->name);
+            } else {
+                if(argm->flags & CLIF_MORE)
+                    fprintf(stderr, " [ %s ...", argm->name);
+                else if(argm->flags & CLIF_ACC_PREV) {
+                    fprintf(stderr, " %s", argm->name);
+                    --deep; /*  ugly, but easy   */
+                } else
+                    fprintf(stderr, " [ %s", argm->name);
+
+                deep++;
+            }
+        }
+
+        if(deep > 0) {
+            fputc(' ', stderr);
+            while(deep--)
+                fputc(']', stderr);
+        }
+    }
+
+    fprintf(stderr, "\n");
+}
+
+int CLIF_current_help(void) {
+
+    if(!curr.argc)
+        return -1; /*  i.e., not inited...  */
+
+    CLIF_print_usage("Usage:", curr.argv[0], curr.option_list,
+            curr.argument_list);
+
+    if(curr.option_list)
+        CLIF_print_options("Options:", curr.option_list);
+
+    if(curr.argument_list)
+        CLIF_print_arguments("\nArguments:", curr.argument_list);
+
+    return 0;
+}
+
+/*  Common useful option handlers.  */
+
+int CLIF_version_handler(CLIF_option *optn, char *arg) {
+    UNUSED(arg);
+    if(!optn->data)
+        return -1;
+
+    fprintf(stderr, "%s\n", ((char *) optn->data));
+
+    return 0; /*  be happy   */
+}
+
+int CLIF_set_flag(CLIF_option *optn, char *arg) {
+    UNUSED(arg);
+    if(!optn->data)
+        return -1;
+
+    *((int *) optn->data) = 1;
+
+    return 0;
+}
+
+int CLIF_unset_flag(CLIF_option *optn, char *arg) {
+    UNUSED(arg);
+    if(!optn->data)
+        return -1;
+
+    *((int *) optn->data) = 0;
+
+    return 0;
+}
+
+static int set_string(char **data, char *arg) {
+
+    if(!data)
+        return -1;
+
+    *data = arg;
+
+    return 0;
+}
+
+int CLIF_set_string(CLIF_option *optn, char *arg) {
+
+    return set_string(optn->data, arg);
+}
+
+int CLIF_arg_string(CLIF_argument *argm, char *arg, int index) {
+    UNUSED(index);
+    return set_string(argm->data, arg);
+}
+
+static int set_int(int *data, char *arg) {
+    char *q;
+
+    if(!data)
+        return -1;
+
+    *data = (int) strtol(arg, &q, 0);
+
+    return (q == arg || *q) ? -1 : 0;
+}
+
+static int set_uint(unsigned int *data, char *arg) {
+    char *q;
+
+    if(!data)
+        return -1;
+
+    *data = (unsigned int) strtoul(arg, &q, 0);
+
+    return (q == arg || *q) ? -1 : 0;
+}
+
+static int set_double(double *data, char *arg) {
+    char *q;
+
+    if(!data)
+        return -1;
+
+    *data = strtod(arg, &q);
+
+    return (q == arg || *q) ? -1 : 0;
+}
+
+int CLIF_set_int(CLIF_option *optn, char *arg) {
+
+    return set_int(optn->data, arg);
+}
+
+int CLIF_set_uint(CLIF_option *optn, char *arg) {
+
+    return set_uint(optn->data, arg);
+}
+
+int CLIF_set_double(CLIF_option *optn, char *arg) {
+
+    return set_double(optn->data, arg);
+}
+
+int CLIF_arg_int(CLIF_argument *argm, char *arg, int index) {
+    UNUSED(index);
+    return set_int(argm->data, arg);
+}
+
+int CLIF_arg_uint(CLIF_argument *argm, char *arg, int index) {
+    UNUSED(index);
+    return set_uint(argm->data, arg);
+}
+
+int CLIF_arg_double(CLIF_argument *argm, char *arg, int index) {
+    UNUSED(index);
+    return set_double(argm->data, arg);
+}
+
+int CLIF_call_func(CLIF_option *optn, char *arg) {
+
+    if(!optn->data)
+        return -1;
+
+    if(optn->arg_name) {
+        int (*func)(char *) = optn->data;
+
+        return func(arg);
+    } else {
+        int (*func)(void) = optn->data;
+
+        return func();
+    }
+}
+
+int CLIF_arg_func(CLIF_argument *argm, char *arg, int index) {
+    int (*func)(char *, int);
+
+    if(!argm->data)
+        return -1;
+
+    func = (int (*)(char *, int)) argm->data;
+
+    return func(arg, index);
+}
+
diff --git a/libdap-chain-net/iputils/traceroute/clif.h b/libdap-chain-net/iputils/traceroute/clif.h
new file mode 100644
index 0000000000000000000000000000000000000000..c798a8df8fbfee7b7450b6e6ec0a46c08443c66f
--- /dev/null
+++ b/libdap-chain-net/iputils/traceroute/clif.h
@@ -0,0 +1,121 @@
+/*
+    Copyright (c)  2000, 2003		Dmitry Butskoy
+					<buc@citadel.stu.neva.ru>
+    License:  LGPL v2.1 or any later
+
+    See COPYING.LIB for the status of this software.
+*/
+
+#ifndef _CLIF_H
+#define _CLIF_H
+
+
+typedef struct CLIF_option_struct CLIF_option;
+struct CLIF_option_struct {
+	const char *short_opt;
+	const char *long_opt;
+	const char *arg_name;
+	const char *help_string;
+	int (*function) (CLIF_option *optn, char *arg);
+	void *data;
+	int (*function_plus) (CLIF_option *optn, char *arg);
+	unsigned int flags;
+};
+#define CLIF_END_OPTION	    { 0, 0, 0, 0, 0, 0, 0, 0 }
+
+typedef struct CLIF_argument_struct CLIF_argument;
+struct CLIF_argument_struct {
+	const char *name;
+	const char *help_string;
+	int (*function) (CLIF_argument *argm, char *arg, int index);
+	void *data;
+	unsigned int flags;
+};
+#define CLIF_END_ARGUMENT   { 0, 0, 0, 0, 0 }
+
+/*  Argument flag bits.  */
+#define CLIF_MORE	(0x01)	/*  null or several  */
+#define CLIF_STRICT	(0x02)	/*  arg must be present   */
+#define CLIF_ACC_PREV	(0x04)  /*  arg must be accompanied with previous  */
+
+
+/*  Option flag bits.  */
+
+/*  affected only by per-option flags   */
+#define CLIF_EXTRA		(0x0001)  /*  don`t show in usage line   */
+#define CLIF_EXIT		(0x0002)  /*  exit after handler return   */
+#define CLIF_EXCL		(0x0004)  /*  at exclusive area  */
+
+/*  affected by per-option flags and by common `parse_flags' argument
+  of CLIF_parse_cmdline(). In last case appropriate bits are translated
+  for all the options.
+*/
+#define CLIF_MAY_JOIN_ARG	(0x0010)
+#define _CLIF_STRICT_JOIN_ARG	(0x0020)
+#define CLIF_JOIN_ARG		(CLIF_MAY_JOIN_ARG|_CLIF_STRICT_JOIN_ARG)
+#define CLIF_MAY_NOEQUAL	(0x0040)
+#define _CLIF_STRICT_NOEQUAL	(0x0080)
+#define CLIF_NOEQUAL		(CLIF_MAY_NOEQUAL|_CLIF_STRICT_NOEQUAL)
+#define CLIF_MAY_KEYWORD	(0x0100)
+#define _CLIF_STRICT_KEYWORD	(0x0200)
+#define CLIF_KEYWORD		(CLIF_MAY_KEYWORD|_CLIF_STRICT_KEYWORD)
+#define CLIF_MAY_ONEDASH	(0x0400)
+#define _CLIF_STRICT_ONEDASH	(0x0800)
+#define CLIF_ONEDASH		(CLIF_MAY_ONEDASH|_CLIF_STRICT_ONEDASH)
+#define CLIF_OPTARG		(0x1000)  /*  allow missing optarg   */
+#define CLIF_ABBREV		(0x2000)  /*  allow long opt abbreviation  */
+#define CLIF_SEVERAL		(0x4000)  /*  several args in one opt`s arg  */
+
+/*  affected only by common `parse_flags' arg of CLIF_parse_cmdline() .  */
+#define CLIF_HELP_EMPTY		(0x10000) /*  print help on empty cmdline  */
+#define CLIF_POSIX		(0x20000) /*  follow POSIX standard  */
+#define CLIF_FIRST_GROUP	(0x40000) /*  first arg - options` group   */
+#define CLIF_STRICT_EXCL	(0x80000) /*  at least one exclusive  */
+#define CLIF_SILENT		(0x100000)	/*  no errors on stderr   */
+
+#define CLIF_MIN_ABBREV	2	/*  a minimal match length in abbrev  */
+
+
+extern int CLIF_parse (int argc, char **argv, CLIF_option *option_list,
+			    CLIF_argument *arg_list, unsigned int parse_flags);
+/*  history compatibility...  */
+#define CLIF_parse_cmdline(ARGC,ARGV,OPTN,ARGS,FLAGS)	\
+		CLIF_parse (ARGC, ARGV, OPTN, ARGS, FLAGS)
+
+extern void CLIF_print_options (const char *header,
+					const CLIF_option *option_list);
+extern void CLIF_print_arguments (const char *header,
+					const CLIF_argument *argument_list);
+extern void CLIF_print_usage (const char *header, const char *progname, 
+					const CLIF_option *option_list,
+					const CLIF_argument *argument_list);
+
+extern int CLIF_current_help (void);
+
+/*  Common useful option handlers.  */
+extern int CLIF_version_handler (CLIF_option *optn, char *arg);
+extern int CLIF_set_flag (CLIF_option *optn, char *arg);
+extern int CLIF_unset_flag (CLIF_option *optn, char *arg);
+extern int CLIF_set_string (CLIF_option *optn, char *arg);
+extern int CLIF_set_int (CLIF_option *optn, char *arg);
+extern int CLIF_set_uint (CLIF_option *optn, char *arg);
+extern int CLIF_set_double (CLIF_option *optn, char *arg);
+extern int CLIF_call_func (CLIF_option *optn, char *arg);
+
+extern int CLIF_arg_string (CLIF_argument *argm, char *arg, int index);
+extern int CLIF_arg_int (CLIF_argument *argm, char *arg, int index);
+extern int CLIF_arg_uint (CLIF_argument *argm, char *arg, int index);
+extern int CLIF_arg_double (CLIF_argument *argm, char *arg, int index);
+extern int CLIF_arg_func (CLIF_argument *argm, char *arg, int index);
+
+
+/*  Some useful macros.  */
+
+#define CLIF_HELP_OPTION    \
+	{ 0, "help", 0, "Read this help and exit",	\
+		CLIF_call_func, CLIF_current_help, 0, CLIF_EXTRA | CLIF_EXIT }
+#define CLIF_VERSION_OPTION(STR)  \
+	{ "V", "version", 0, "Print version info and exit",	\
+		CLIF_version_handler, STR, 0, CLIF_EXTRA | CLIF_EXIT }
+
+#endif	/*  _CLIF_H   */
diff --git a/libdap-chain-net/iputils/traceroute/csum.c b/libdap-chain-net/iputils/traceroute/csum.c
new file mode 100644
index 0000000000000000000000000000000000000000..ea3a50da0430d19582941d72cded69fb09e45127
--- /dev/null
+++ b/libdap-chain-net/iputils/traceroute/csum.c
@@ -0,0 +1,34 @@
+/*
+    Copyright (c)  2006, 2007		Dmitry Butskoy
+					<buc@citadel.stu.neva.ru>
+    License:  GPL v2 or any later
+
+    See COPYING for the status of this software.
+*/
+
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "traceroute.h"
+
+
+uint16_t in_csum (const void *ptr, size_t len) {
+	const uint16_t *p = (const uint16_t *) ptr;
+	size_t nw = len / 2;
+	unsigned int sum = 0;
+	uint16_t res;
+
+	while (nw--)  sum += *p++;
+
+	if (len & 0x1)
+	    sum += htons (*((unsigned char *) p) << 8);
+
+	sum = (sum >> 16) + (sum & 0xffff);
+	sum += (sum >> 16);
+
+	res = ~sum;
+	if (!res)  res = ~0;
+
+	return res;
+}
+
diff --git a/libdap-chain-net/iputils/traceroute/extension.c b/libdap-chain-net/iputils/traceroute/extension.c
new file mode 100644
index 0000000000000000000000000000000000000000..515a8dbed7da85addd25543a3bbcfd303944f667
--- /dev/null
+++ b/libdap-chain-net/iputils/traceroute/extension.c
@@ -0,0 +1,132 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "traceroute.h"
+
+
+struct icmp_ext_header {
+#if __BYTE_ORDER == __BIG_ENDIAN
+	unsigned int version:4;
+	unsigned int reserved:4;
+#else
+	unsigned int reserved:4;
+	unsigned int version:4;
+#endif
+	uint8_t reserved1;
+	uint16_t checksum;
+} __attribute__ ((packed));
+
+
+struct icmp_ext_object {
+	uint16_t length;
+	uint8_t class;
+	uint8_t c_type;
+	uint8_t data[0];
+};
+
+#define MPLS_CLASS 1
+#define MPLS_C_TYPE 1
+
+
+#define do_snprintf(CURR, END, FMT, ARGS...)	\
+	do {					\
+	    CURR += snprintf (CURR, END - CURR, (FMT), ## ARGS);\
+	    if (CURR > END)  CURR = END;			\
+	} while (0)
+
+
+static int try_extension (probe *pb, char *buf, size_t len) {
+	struct icmp_ext_header *iext = (struct icmp_ext_header *) buf;
+	char str[1024];
+	char *curr = str;
+	char *end = str + sizeof (str) / sizeof (*str);
+	
+
+	/*  a check for len >= 8 already done for all cases   */
+
+	if (iext->version != 2)  return -1;
+
+	if (iext->checksum &&
+	    in_csum (iext, len) != (uint16_t) ~0
+	)  return -1;
+
+	buf += sizeof (*iext);
+	len -= sizeof (*iext);
+
+
+	while (len >= sizeof (struct icmp_ext_object)) {
+	    struct icmp_ext_object *obj = (struct icmp_ext_object *) buf;
+	    size_t objlen = ntohs (obj->length);
+	    size_t data_len;
+	    uint32_t *ui = (uint32_t *) obj->data;
+	    int i, n;
+
+	    if (objlen < sizeof (*obj) ||
+		objlen > len
+	    )  return -1;
+
+	    data_len = objlen - sizeof (*obj);
+	    if (data_len % sizeof (uint32_t))
+		    return -1;	/*  must be 32bit rounded...  */
+
+	    n = data_len / sizeof (*ui);
+
+
+	    if (curr > (char *) str && curr < end)
+		    *curr++ = ';';	/*  a separator   */
+
+	    if (obj->class == MPLS_CLASS &&
+		obj->c_type == MPLS_C_TYPE &&
+		n >= 1
+	    ) {    /*  people prefer MPLS to be parsed...  */
+
+		do_snprintf (curr, end, "MPLS:");
+
+		for (i = 0; i < n; i++, ui++) {
+		    uint32_t mpls = ntohl (*ui);
+
+		    do_snprintf (curr, end, "%sL=%u,E=%u,S=%u,T=%u",
+					i ? "/" : "",
+					mpls >> 12,
+					(mpls >> 9) & 0x7,
+					(mpls >> 8) & 0x1,
+					mpls & 0xff);
+		}
+
+	    }
+	    else {	/*  common case...  */
+
+		do_snprintf (curr, end, "%u/%u:", obj->class, obj->c_type);
+
+		for (i = 0; i < n && curr < end; i++, ui++)
+		    do_snprintf (curr, end, "%s%08x", i ? "," : "", ntohl(*ui));
+	    }
+
+	    buf += objlen;
+	    len -= objlen;
+	}
+
+	if (len)  return -1;
+
+
+	pb->ext = strdup (str);
+
+	return 0;
+}
+
+
+void handle_extensions (probe *pb, char *buf, int len, int step) {
+
+	if (!step)
+	    try_extension (pb, buf, len);
+	else {
+	    for ( ; len >= 8; buf += step, len -= step)
+		if (try_extension (pb, buf, len) == 0)
+			break;
+	}
+
+	return;
+}
+
diff --git a/libdap-chain-net/iputils/traceroute/flowlabel.h b/libdap-chain-net/iputils/traceroute/flowlabel.h
new file mode 100755
index 0000000000000000000000000000000000000000..e6241c4f8e9cdd00558ffd3494cfe1b5d40a1c80
--- /dev/null
+++ b/libdap-chain-net/iputils/traceroute/flowlabel.h
@@ -0,0 +1,40 @@
+/*
+   It is just a stripped copy of the kernel header "linux/in6.h"
+
+   "Flow label" things are still not defined in "netinet/in*.h" headers,
+   but we cannot use "linux/in6.h" immediately because it currently
+   conflicts with "netinet/in.h" .
+*/
+
+struct in6_flowlabel_req_
+{
+	struct in6_addr	flr_dst;
+	__u32	flr_label;
+	__u8	flr_action;
+	__u8	flr_share;
+	__u16	flr_flags;
+	__u16 	flr_expires;
+	__u16	flr_linger;
+	__u32	__flr_pad;
+	/* Options in format of IPV6_PKTOPTIONS */
+};
+
+#define IPV6_FL_A_GET	0
+#define IPV6_FL_A_PUT	1
+#define IPV6_FL_A_RENEW	2
+
+#define IPV6_FL_F_CREATE	1
+#define IPV6_FL_F_EXCL		2
+
+#define IPV6_FL_S_NONE		0
+#define IPV6_FL_S_EXCL		1
+#define IPV6_FL_S_PROCESS	2
+#define IPV6_FL_S_USER		3
+#define IPV6_FL_S_ANY		255
+
+#define IPV6_FLOWINFO_FLOWLABEL		0x000fffff
+#define IPV6_FLOWINFO_PRIORITY		0x0ff00000
+
+#define IPV6_FLOWLABEL_MGR	32
+#define IPV6_FLOWINFO_SEND	33
+
diff --git a/libdap-chain-net/iputils/traceroute/mod-dccp.c b/libdap-chain-net/iputils/traceroute/mod-dccp.c
new file mode 100644
index 0000000000000000000000000000000000000000..26f2c653bf0fd3af1faeb911cd943e493edd93e9
--- /dev/null
+++ b/libdap-chain-net/iputils/traceroute/mod-dccp.c
@@ -0,0 +1,295 @@
+/*
+    Copyright (c)  2012		Samuel Jero <sj323707@ohio.edu>
+    License:  GPL v2 or any later
+
+    See COPYING for the status of this software.
+*/
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <poll.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <linux/dccp.h>
+
+
+#include "traceroute.h"
+
+
+#define DEF_SERVICE_CODE 	1885957735
+
+#define DCCP_HEADER_LEN		(sizeof (struct dccp_hdr) + \
+				 sizeof (struct dccp_hdr_ext) \
+				 + sizeof (struct dccp_hdr_request))
+
+static sockaddr_any dest_addr = {{ 0, }, };
+static unsigned int dest_port = 0;
+
+static int raw_sk = -1;
+static int last_ttl = 0;
+
+static uint8_t buf[1024];	/*  enough, enough...  */
+static size_t csum_len = 0;
+static struct dccp_hdr *dh = NULL;
+static struct dccp_hdr_ext *dhe = NULL;
+static struct dccp_hdr_request *dhr = NULL;
+static unsigned int service_code = DEF_SERVICE_CODE;
+
+
+static CLIF_option dccp_options[] = {
+	{ 0, "service", "NUM", "Set DCCP service code to %s (default is "
+				_TEXT (DEF_SERVICE_CODE) ")",
+				CLIF_set_uint, &service_code, 0, CLIF_ABBREV },
+	CLIF_END_OPTION
+};
+
+
+static int dccp_init (const sockaddr_any *dest,
+				unsigned int port_seq, size_t *packet_len_p) {
+	int af = dest->sa.sa_family;
+	sockaddr_any src;
+	socklen_t len;
+	uint8_t *ptr;
+	uint16_t *lenp;
+
+
+	dest_addr = *dest;
+	dest_addr.sin.sin_port = 0;	/*  raw sockets can be confused   */
+
+	if (!port_seq)  port_seq = DEF_DCCP_PORT;
+	dest_port = htons (port_seq);
+
+
+	/*  Create raw socket for DCCP   */
+	raw_sk = socket (af, SOCK_RAW, IPPROTO_DCCP);
+	if (raw_sk < 0)
+		error_or_perm ("socket");
+
+	tune_socket (raw_sk);	    /*  including bind, if any   */
+
+	if (connect (raw_sk, &dest_addr.sa, sizeof (dest_addr)) < 0)
+		error ("connect");
+
+	len = sizeof (src);
+	if (getsockname (raw_sk, &src.sa, &len) < 0)
+		error ("getsockname");
+
+
+	if (!raw_can_connect ()) {	/*  work-around for buggy kernels  */
+		close (raw_sk);
+		raw_sk = socket (af, SOCK_RAW, IPPROTO_DCCP);
+		if (raw_sk < 0)  error ("socket");
+		tune_socket (raw_sk);
+		/*  but do not connect it...  */
+	}
+
+
+	use_recverr (raw_sk);
+
+	add_poll (raw_sk, POLLIN | POLLERR);
+
+
+	/*  Now create the sample packet.  */
+
+	/*  For easy checksum computing:
+		saddr
+		daddr
+		length
+		protocol
+		dccphdr
+	*/
+
+	ptr = buf;
+
+	if (af == AF_INET) {
+		len = sizeof (src.sin.sin_addr);
+		memcpy (ptr, &src.sin.sin_addr, len);
+		ptr += len;
+		memcpy (ptr, &dest_addr.sin.sin_addr, len);
+		ptr += len;
+	} else {
+		len = sizeof (src.sin6.sin6_addr);
+		memcpy (ptr, &src.sin6.sin6_addr, len);
+		ptr += len;
+		memcpy (ptr, &dest_addr.sin6.sin6_addr, len);
+		ptr += len;
+	}
+
+	lenp = (uint16_t *) ptr;
+	ptr += sizeof (uint16_t);
+	*((uint16_t *) ptr) = htons ((uint16_t) IPPROTO_DCCP);
+	ptr += sizeof (uint16_t);
+
+
+	/*  Construct DCCP header   */
+	dh = (struct dccp_hdr *) ptr;
+
+	dh->dccph_ccval = 0;
+	dh->dccph_checksum = 0;
+	dh->dccph_cscov = 0;
+	dh->dccph_dport = dest_port;
+	dh->dccph_reserved = 0;
+	dh->dccph_sport = 0;	/*  temporary   */
+	dh->dccph_x = 1;
+	dh->dccph_type = DCCP_PKT_REQUEST;
+	dh->dccph_seq2 = 0;	/*  reserved if using 48 bit sequence numbers  */
+	/*  high 16 bits of sequence number. Always make 0 for simplicity.  */
+	dh->dccph_seq = 0;
+	ptr += sizeof (struct dccp_hdr);
+
+	dhe = (struct dccp_hdr_ext *) ptr;
+	dhe->dccph_seq_low = 0;		/*  temporary   */
+	ptr += sizeof (struct dccp_hdr_ext);
+
+	dhr = (struct dccp_hdr_request *) ptr;
+	dhr->dccph_req_service = htonl (service_code);
+	ptr += sizeof (struct dccp_hdr_request);
+
+
+	csum_len = ptr - buf;
+
+	if (csum_len > sizeof (buf))
+		error ("impossible");	/*  paranoia   */
+
+	len = ptr - (uint8_t *) dh;
+	if (len & 0x03)  error ("impossible");  /*  as >>2 ...  */
+
+	*lenp = htons (len);
+	dh->dccph_doff = len >> 2;
+
+
+	*packet_len_p = len;
+
+	return 0;
+}
+
+
+static void dccp_send_probe (probe *pb, int ttl) {
+	int sk;
+	int af = dest_addr.sa.sa_family;
+	sockaddr_any addr;
+	socklen_t len = sizeof (addr);
+
+
+	/*  To make sure we have chosen a free unused "source port",
+	   just create, (auto)bind and hold a socket while the port is needed.
+	*/
+
+	sk = socket (af, SOCK_DCCP, IPPROTO_DCCP);
+	if (sk < 0)  error ("socket");
+
+	bind_socket (sk);
+
+	if (getsockname (sk, &addr.sa, &len) < 0)
+		error ("getsockname");
+
+	/*  When we reach the target host, it can send us either Reset or Response.
+	  For Reset all is OK (we and kernel just answer nothing), but
+	  for Response we should reply with our Close.
+	    It is well-known "half-open technique", used by port scanners etc.
+	  This way we do not touch remote applications at all, unlike
+	  the ordinary connect(2) call.
+	    As the port-holding socket neither connect() nor listen(),
+	  it means "no such port yet" for remote ends, and kernel always
+	  send Reset in such a situation automatically (we have to do nothing).
+	*/
+
+
+	dh->dccph_sport = addr.sin.sin_port;
+
+	dhe->dccph_seq_low = random_seq ();
+
+	dh->dccph_checksum = 0;
+	dh->dccph_checksum = in_csum (buf, csum_len);
+
+
+	if (ttl != last_ttl) {
+		set_ttl (raw_sk, ttl);
+		last_ttl = ttl;
+	}
+
+
+	pb->send_time = get_time ();
+
+	if (do_send (raw_sk, dh, dh->dccph_doff << 2, &dest_addr) < 0) {
+		close (sk);
+		pb->send_time = 0;
+		return;
+	}
+
+
+	pb->seq = dh->dccph_sport;
+
+	pb->sk = sk;
+
+	return;
+}
+
+
+static probe *dccp_check_reply (int sk, int err, sockaddr_any *from,
+						    char *buf, size_t len) {
+    UNUSED(sk);
+	probe *pb;
+	struct dccp_hdr *ndh = (struct dccp_hdr *) buf;
+	uint16_t sport, dport;
+
+
+	if (len < 8)  return NULL;	    /*  too short   */
+
+
+	if (err) {
+	    sport = ndh->dccph_sport;
+	    dport = ndh->dccph_dport;
+	} else {
+	    sport = ndh->dccph_dport;
+	    dport = ndh->dccph_sport;
+	}
+
+
+	if (dport != dest_port)
+		return NULL;
+
+	if (!equal_addr (&dest_addr, from))
+		return NULL;
+
+	pb = probe_by_seq (sport);
+	if (!pb)  return NULL;
+
+	if (!err)  pb->final = 1;
+
+	return pb;
+}
+
+
+static void dccp_recv_probe (int sk, int revents) {
+
+	if (!(revents & (POLLIN | POLLERR)))
+		return;
+
+	recv_reply (sk, !!(revents & POLLERR), dccp_check_reply);
+}
+
+
+static void dccp_expire_probe (probe *pb) {
+
+	probe_done (pb);
+}
+
+
+static tr_module dccp_ops = {
+	.name = "dccp",
+	.init = dccp_init,
+	.send_probe = dccp_send_probe,
+	.recv_probe = dccp_recv_probe,
+	.expire_probe = dccp_expire_probe,
+	.options = dccp_options,
+};
+
+TR_MODULE (dccp_ops);
+
+void tr_module_dccp_insert()
+{
+
+}
diff --git a/libdap-chain-net/iputils/traceroute/mod-icmp.c b/libdap-chain-net/iputils/traceroute/mod-icmp.c
new file mode 100644
index 0000000000000000000000000000000000000000..e2194d9aa4563b22b48d66f324de9f8cedf61b00
--- /dev/null
+++ b/libdap-chain-net/iputils/traceroute/mod-icmp.c
@@ -0,0 +1,240 @@
+/*
+ Copyright (c)  2006, 2007		Dmitry Butskoy
+ <buc@citadel.stu.neva.ru>
+ License:  GPL v2 or any later
+
+ See COPYING for the status of this software.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <poll.h>
+#include <netinet/icmp6.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+
+#include "traceroute.h"
+
+static sockaddr_any dest_addr = { { 0, }, };
+static uint16_t seq = 1;
+static uint16_t ident = 0;
+
+static char *data;
+static size_t *length_p;
+
+static int icmp_sk = -1;
+static int last_ttl = 0;
+
+static int raw = 0;
+static int dgram = 0;
+
+static CLIF_option icmp_options[] = {
+    { 0, "raw", 0, "Use raw sockets way only. Default is try this way "
+            "first (probably not allowed for unprivileged users), "
+            "then try dgram",
+        CLIF_set_flag, &raw, 0, CLIF_EXCL },
+    { 0, "dgram", 0, "Use dgram sockets way only. May be not implemented "
+            "by old kernels or restricted by sysadmins",
+        CLIF_set_flag, &dgram, 0, CLIF_EXCL },
+    CLIF_END_OPTION
+    };
+
+static int icmp_init(const sockaddr_any *dest,
+        unsigned int port_seq, size_t *packet_len_p) {
+    int i;
+    int af = dest->sa.sa_family;
+    int protocol;
+
+    dest_addr = *dest;
+    dest_addr.sin.sin_port = 0;
+
+    if(port_seq)
+        seq = port_seq;
+
+    length_p = packet_len_p;
+    if(*length_p < sizeof(struct icmphdr))
+        *length_p = sizeof(struct icmphdr);
+
+    data = malloc(*length_p);
+    if(!data)
+        error("malloc");
+
+    for(i = (int) sizeof(struct icmphdr); i < (int)*length_p; i++)
+        data[i] = 0x40 + (i & 0x3f);
+
+    protocol = (af == AF_INET) ? IPPROTO_ICMP : IPPROTO_ICMPV6;
+
+    if(!raw) {
+        icmp_sk = socket(af, SOCK_DGRAM, protocol);
+        if(icmp_sk < 0 && dgram)
+            error("socket");
+    }
+
+    if(!dgram) {
+        int raw_sk = socket(af, SOCK_RAW, protocol);
+        if(raw_sk < 0) {
+            if(raw || icmp_sk < 0)
+                error_or_perm("socket");
+            dgram = 1;
+        } else {
+            /*  prefer the traditional "raw" way when possible   */
+            close(icmp_sk);
+            icmp_sk = raw_sk;
+        }
+    }
+
+    tune_socket(icmp_sk);
+
+    /*  Don't want to catch packets from another hosts   */
+    if(raw_can_connect() &&
+            connect(icmp_sk, &dest_addr.sa, sizeof(dest_addr)) < 0
+                    )
+        error("connect");
+
+    use_recverr(icmp_sk);
+
+    if(dgram) {
+        sockaddr_any addr;
+        socklen_t len = sizeof(addr);
+
+        if(getsockname(icmp_sk, &addr.sa, &len) < 0)
+            error("getsockname");
+        ident = ntohs(addr.sin.sin_port); /*  both IPv4 and IPv6   */
+
+    } else
+        ident = getpid() & 0xffff;
+
+    add_poll(icmp_sk, POLLIN | POLLERR);
+
+    return 0;
+}
+
+static void icmp_send_probe(probe *pb, int ttl) {
+    int af = dest_addr.sa.sa_family;
+
+    if(ttl != last_ttl) {
+
+        set_ttl(icmp_sk, ttl);
+
+        last_ttl = ttl;
+    }
+
+    if(af == AF_INET) {
+        struct icmp *icmp = (struct icmp *) data;
+
+        icmp->icmp_type = ICMP_ECHO;
+        icmp->icmp_code = 0;
+        icmp->icmp_cksum = 0;
+        icmp->icmp_id = htons(ident);
+        icmp->icmp_seq = htons(seq);
+
+        icmp->icmp_cksum = in_csum(data, *length_p);
+    }
+    else if(af == AF_INET6) {
+        struct icmp6_hdr *icmp6 = (struct icmp6_hdr *) data;
+
+        icmp6->icmp6_type = ICMP6_ECHO_REQUEST;
+        icmp6->icmp6_code = 0;
+        icmp6->icmp6_cksum = 0;
+        icmp6->icmp6_id= htons (ident);
+        icmp6->icmp6_seq= htons(seq);
+
+        /*  icmp6->icmp6_cksum always computed by kernel internally   */
+    }
+
+    pb->send_time = get_time();
+
+    if(do_send(icmp_sk, data, *length_p, &dest_addr) < 0) {
+        pb->send_time = 0;
+        return;
+    }
+
+    pb->seq = seq;
+
+    seq++;
+
+    return;
+}
+
+static probe *icmp_check_reply(int sk, int err, sockaddr_any *from,
+        char *buf, size_t len) {
+    UNUSED(sk);
+    UNUSED(from);
+    int af = dest_addr.sa.sa_family;
+    int type;
+    uint16_t recv_id, recv_seq;
+    probe *pb;
+
+    if(len < sizeof(struct icmphdr))
+        return NULL;
+
+    if(af == AF_INET) {
+        struct icmp *icmp = (struct icmp *) buf;
+
+        type = icmp->icmp_type;
+
+        recv_id = ntohs(icmp->icmp_id);
+        recv_seq = ntohs(icmp->icmp_seq);
+
+    }
+    else { /*  AF_INET6   */
+        struct icmp6_hdr *icmp6 = (struct icmp6_hdr *) buf;
+
+        type = icmp6->icmp6_type;
+
+        recv_id = ntohs(icmp6->icmp6_id);
+        recv_seq = ntohs(icmp6->icmp6_seq);
+    }
+
+    if(recv_id != ident)
+        return NULL;
+
+    pb = probe_by_seq(recv_seq);
+    if(!pb)
+        return NULL;
+
+    if(!err) {
+
+        if(!(af == AF_INET && type == ICMP_ECHOREPLY) &&
+                !(af == AF_INET6 && type == ICMP6_ECHO_REPLY)
+                )
+            return NULL;
+
+        pb->final = 1;
+    }
+
+    return pb;
+}
+
+static void icmp_recv_probe(int sk, int revents) {
+
+    if(!(revents & (POLLIN | POLLERR)))
+        return;
+
+    recv_reply(sk, !!(revents & POLLERR), icmp_check_reply);
+}
+
+static void icmp_expire_probe(probe *pb) {
+
+    probe_done(pb);
+}
+
+static tr_module icmp_ops = {
+    .name = "icmp",
+    .init = icmp_init,
+    .send_probe = icmp_send_probe,
+    .recv_probe = icmp_recv_probe,
+    .expire_probe = icmp_expire_probe,
+    .options = icmp_options,
+};
+
+TR_MODULE(icmp_ops);
+
+void tr_module_icmp_insert()
+{
+
+}
+
diff --git a/libdap-chain-net/iputils/traceroute/mod-raw.c b/libdap-chain-net/iputils/traceroute/mod-raw.c
new file mode 100644
index 0000000000000000000000000000000000000000..bd9f05ba612569e5fbfdb1a3aec01990a55dd8a0
--- /dev/null
+++ b/libdap-chain-net/iputils/traceroute/mod-raw.c
@@ -0,0 +1,172 @@
+/*
+    Copyright (c)  2006, 2007		Dmitry Butskoy
+					<buc@citadel.stu.neva.ru>
+    License:  GPL v2 or any later
+
+    See COPYING for the status of this software.
+*/
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <poll.h>
+#include <netinet/icmp6.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <netdb.h>
+
+#include "traceroute.h"
+
+
+static sockaddr_any dest_addr = {{ 0, }, };
+static int protocol = DEF_RAW_PROT;
+
+static char *data = NULL;
+static size_t *length_p;
+
+static int raw_sk = -1;
+static int last_ttl = 0;
+static int seq = 0;
+
+
+static int set_protocol (CLIF_option *optn, char *arg) {
+    UNUSED(optn);
+    char *q;
+
+	protocol = strtoul (arg, &q, 0);
+	if (q == arg) {
+	    struct protoent *p = getprotobyname (arg);
+
+	    if (!p)  return -1;
+	    protocol = p->p_proto;
+	}
+
+	return 0;
+}
+
+
+static CLIF_option raw_options[] = {
+	{ 0, "protocol", "PROT", "Use protocol %s (default is "
+			_TEXT (DEF_RAW_PROT) ")",
+			set_protocol, 0, 0, CLIF_ABBREV },
+	CLIF_END_OPTION
+};
+
+
+static int raw_init (const sockaddr_any *dest,
+			    unsigned int port_seq, size_t *packet_len_p) {
+	int i;
+	int af = dest->sa.sa_family;
+
+	dest_addr = *dest;
+	dest_addr.sin.sin_port = 0;
+
+	if (port_seq)  protocol = port_seq;
+
+
+	length_p = packet_len_p;
+
+	if (*length_p &&
+	    !(data = malloc (*length_p))
+	)  error ("malloc");
+
+        for (i = 0; i < (int)*length_p; i++)
+                data[i] = 0x40 + (i & 0x3f);
+
+
+	raw_sk = socket (af, SOCK_RAW, protocol);
+	if (raw_sk < 0)
+		error_or_perm ("socket");
+
+	tune_socket (raw_sk);
+
+	/*  Don't want to catch packets from another hosts   */
+	if (raw_can_connect () &&
+	    connect (raw_sk, &dest_addr.sa, sizeof (dest_addr)) < 0
+	)  error ("connect");
+
+	use_recverr (raw_sk);
+
+
+	add_poll (raw_sk, POLLIN | POLLERR);
+
+	return 0;
+}
+
+
+static void raw_send_probe (probe *pb, int ttl) {
+
+	if (ttl != last_ttl) {
+
+	    set_ttl (raw_sk, ttl);
+
+	    last_ttl = ttl;
+	}
+
+
+	pb->send_time = get_time ();
+
+	if (do_send (raw_sk, data, *length_p, &dest_addr) < 0) {
+	    pb->send_time = 0;
+	    return;
+	}
+
+
+	pb->seq = ++seq;
+
+	return;
+}
+
+
+static probe *raw_check_reply (int sk, int err, sockaddr_any *from,
+						    char *buf, size_t len) {
+    UNUSED(sk);
+    UNUSED(len);
+    UNUSED(buf);
+    probe *pb;
+
+	if (!equal_addr (&dest_addr, from))
+		return NULL;
+
+	pb = probe_by_seq (seq);
+	if (!pb)  return NULL;
+
+	if (!err)  pb->final = 1;
+
+	return pb;
+}
+
+
+static void raw_recv_probe (int sk, int revents) {
+
+	if (!(revents & (POLLIN | POLLERR)))
+		return;
+
+	recv_reply (sk, !!(revents & POLLERR), raw_check_reply);
+}
+
+
+static void raw_expire_probe (probe *pb) {
+
+	probe_done (pb);
+}
+
+
+static tr_module raw_ops = {
+	.name = "raw",
+	.init = raw_init,
+	.send_probe = raw_send_probe,
+	.recv_probe = raw_recv_probe,
+	.expire_probe = raw_expire_probe,
+	.options = raw_options,
+	.one_per_time = 1,
+};
+
+TR_MODULE (raw_ops);
+
+void tr_module_raw_insert()
+{
+
+}
diff --git a/libdap-chain-net/iputils/traceroute/mod-tcp.c b/libdap-chain-net/iputils/traceroute/mod-tcp.c
new file mode 100644
index 0000000000000000000000000000000000000000..32d2f1027e88db3ff623150cfcca5b7aad7ccf8d
--- /dev/null
+++ b/libdap-chain-net/iputils/traceroute/mod-tcp.c
@@ -0,0 +1,515 @@
+/*
+    Copyright (c)  2006, 2007		Dmitry Butskoy
+					<buc@citadel.stu.neva.ru>
+    License:  GPL v2 or any later
+
+    See COPYING for the status of this software.
+*/
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <poll.h>
+#include <netinet/icmp6.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <netinet/tcp.h>
+
+
+#include "traceroute.h"
+
+
+#ifndef IP_MTU
+#define IP_MTU	14
+#endif
+
+
+static sockaddr_any dest_addr = {{ 0, }, };
+static unsigned int dest_port = 0;
+
+static int raw_sk = -1;
+static int last_ttl = 0;
+
+static uint8_t buf[1024];	    /*  enough, enough...  */
+static size_t csum_len = 0;
+static struct tcphdr *th = NULL;
+
+#define TH_FLAGS(TH)	(((uint8_t *) (TH))[13])
+#define TH_FIN	0x01
+#define TH_SYN	0x02
+#define TH_RST	0x04
+#define TH_PSH	0x08
+#define TH_ACK	0x10
+#define TH_URG	0x20
+#define TH_ECE	0x40
+#define TH_CWR	0x80
+
+
+static int flags = 0;	    /*  & 0xff == tcp_flags ...  */
+static int sysctl = 0;
+static int reuse = 0;
+static unsigned int mss = 0;
+static int info = 0;
+
+#define FL_FLAGS	0x0100
+#define FL_ECN		0x0200
+#define FL_SACK		0x0400
+#define FL_TSTAMP	0x0800
+#define FL_WSCALE	0x1000
+
+
+static struct {
+	const char *name;
+	unsigned int flag;
+} tcp_flags[] = {
+	{ "fin", TH_FIN },
+	{ "syn", TH_SYN },
+	{ "rst", TH_RST },
+	{ "psh", TH_PSH },
+	{ "ack", TH_ACK },
+	{ "urg", TH_URG },
+	{ "ece", TH_ECE },
+	{ "cwr", TH_CWR },
+};
+
+static char *names_by_flags (unsigned int flags) {
+	int i;
+	char str[64];	/*  enough...  */
+	char *curr = str;
+	char *end = str + sizeof (str) / sizeof (*str);
+
+	for (i = 0; i < (int)(sizeof (tcp_flags) / sizeof (*tcp_flags)); i++) {
+	    const char *p;
+
+	    if (!(flags & tcp_flags[i].flag))  continue;
+
+	    if (curr > str && curr < end)  *curr++ = ',';
+	    for (p = tcp_flags[i].name; *p && curr < end; *curr++ = *p++) ;
+	}
+
+	*curr = '\0';
+
+	return  strdup (str);
+}
+
+static int set_tcp_flag (CLIF_option *optn, char *arg) {
+    UNUSED(arg);
+    int i;
+
+	for (i = 0; i < (int)(sizeof (tcp_flags) / sizeof (*tcp_flags)); i++) {
+	    if (!strcmp (optn->long_opt, tcp_flags[i].name)) {
+		    flags |= tcp_flags[i].flag;
+		    return 0;
+	    }
+	}
+
+	return -1;
+}
+
+static int set_tcp_flags (CLIF_option *optn, char *arg) {
+    UNUSED(optn);
+    char *q;
+	unsigned long value;
+
+	value = strtoul (arg, &q, 0);
+	if (q == arg)  return -1;
+
+	flags = (flags & ~0xff) | (value & 0xff) | FL_FLAGS;
+	return 0;
+}
+
+static int set_flag (CLIF_option *optn, char *arg) {
+    UNUSED(arg);
+	flags |= (unsigned long) optn->data;
+
+	return 0;
+}
+
+static CLIF_option tcp_options[] = {
+	{ 0, "syn", 0, "Set tcp flag SYN (default if no other "
+			"tcp flags specified)", set_tcp_flag, 0, 0, 0 },
+	{ 0, "ack", 0, "Set tcp flag ACK,", set_tcp_flag, 0, 0, 0 },
+	{ 0, "fin", 0, "FIN,", set_tcp_flag, 0, 0, 0 },
+	{ 0, "rst", 0, "RST,", set_tcp_flag, 0, 0, 0 },
+	{ 0, "psh", 0, "PSH,", set_tcp_flag, 0, 0, 0 },
+	{ 0, "urg", 0, "URG,", set_tcp_flag, 0, 0, 0 },
+	{ 0, "ece", 0, "ECE,", set_tcp_flag, 0, 0, 0 },
+	{ 0, "cwr", 0, "CWR", set_tcp_flag, 0, 0, 0 },
+	{ 0, "flags", "NUM", "Set tcp flags exactly to value %s",
+				set_tcp_flags, 0, 0, CLIF_ABBREV },
+	{ 0, "ecn", 0, "Send syn packet with tcp flags ECE and CWR "
+			"(for Explicit Congestion Notification, rfc3168)",
+				set_flag, (void *) FL_ECN, 0, 0 },
+	{ 0, "sack", 0, "Use sack,",
+				set_flag, (void *) FL_SACK, 0, 0 },
+	{ 0, "timestamps", 0, "timestamps,",
+				set_flag, (void *) FL_TSTAMP, 0, CLIF_ABBREV },
+	{ 0, "window_scaling", 0, "window_scaling option for tcp",
+				set_flag, (void *) FL_WSCALE, 0, CLIF_ABBREV },
+	{ 0, "sysctl", 0, "Use current sysctl (/proc/sys/net/*) setting "
+			"for the tcp options and ecn. Always set by default "
+			"(with \"syn\") if nothing else specified",
+				CLIF_set_flag, &sysctl, 0, 0 },
+	{ 0, "reuse", 0, "Allow to reuse local port numbers "
+			"for the huge workloads (SO_REUSEADDR)",
+				CLIF_set_flag, &reuse, 0, 0 },
+	{ 0, "mss", "NUM", "Use value of %s for maxseg tcp option (when syn)",
+				CLIF_set_uint, &mss, 0, 0 },
+	{ 0, "info", 0, "Print tcp flags of final tcp replies when target "
+			"host is reached. Useful to determine whether "
+			"an application listens the port etc.",
+				CLIF_set_flag, &info, 0, 0 },
+	CLIF_END_OPTION
+};
+
+
+#define SYSCTL_PREFIX	"/proc/sys/net/ipv4/tcp_"
+static int check_sysctl (const char *name) {
+	int fd, res;
+	char buf[sizeof (SYSCTL_PREFIX) + strlen (name) + 1];
+	uint8_t ch;
+
+	strcpy (buf, SYSCTL_PREFIX);
+	strcat (buf, name);
+
+	fd = open (buf, O_RDONLY, 0);
+	if (fd < 0)  return 0;
+
+	res = read (fd, &ch, sizeof (ch));
+	close (fd);
+
+	if (res != sizeof (ch))
+		return 0;
+
+	/*  since kernel 2.6.31 "tcp_ecn" can have value of '2'...  */
+	if (ch == '1')  return 1;
+
+	return 0;
+}
+
+
+static int tcp_init (const sockaddr_any *dest,
+			    unsigned int port_seq, size_t *packet_len_p) {
+	int af = dest->sa.sa_family;
+	sockaddr_any src;
+	int mtu;
+	socklen_t len;
+	uint8_t *ptr;
+	uint16_t *lenp;
+
+
+	dest_addr = *dest;
+	dest_addr.sin.sin_port = 0;	/*  raw sockets can be confused   */
+
+	if (!port_seq)  port_seq = DEF_TCP_PORT;
+	dest_port = htons (port_seq);
+
+
+	/*  Create raw socket for tcp   */
+
+	raw_sk = socket (af, SOCK_RAW, IPPROTO_TCP);
+	if (raw_sk < 0)
+		error_or_perm ("socket");
+
+	tune_socket (raw_sk);	    /*  including bind, if any   */
+
+	if (connect (raw_sk, &dest_addr.sa, sizeof (dest_addr)) < 0)
+		error ("connect");
+
+	len = sizeof (src);
+	if (getsockname (raw_sk, &src.sa, &len) < 0)
+		error ("getsockname");
+
+
+	len = sizeof (mtu);
+	if (getsockopt (raw_sk, af == AF_INET ? SOL_IP : SOL_IPV6,
+				af == AF_INET ? IP_MTU : IPV6_MTU,
+				&mtu, &len) < 0 || mtu < 576
+	)  mtu = 576;
+
+	/*  mss = mtu - headers   */
+	mtu -= af == AF_INET ? sizeof (struct iphdr) : sizeof (struct ip6_hdr);
+	mtu -= sizeof (struct tcphdr);
+
+
+	if (!raw_can_connect ()) {	/*  work-around for buggy kernels  */
+	    close (raw_sk);
+	    raw_sk = socket (af, SOCK_RAW, IPPROTO_TCP);
+	    if (raw_sk < 0)  error ("socket");
+	    tune_socket (raw_sk);
+	    /*  but do not connect it...  */
+	}
+
+
+	use_recverr (raw_sk);
+
+	add_poll (raw_sk, POLLIN | POLLERR);
+
+
+	/*  Now create the sample packet.  */
+
+	if (!flags)  sysctl = 1;
+
+	if (sysctl) {
+	    if (check_sysctl ("ecn"))  flags |= FL_ECN;
+	    if (check_sysctl ("sack"))  flags |= FL_SACK;
+	    if (check_sysctl ("timestamps"))  flags |= FL_TSTAMP;
+	    if (check_sysctl ("window_scaling"))  flags |= FL_WSCALE;
+	}
+
+	if (!(flags & (FL_FLAGS | 0xff))) {	/*  no any tcp flag set   */
+	    flags |= TH_SYN;
+	    if (flags & FL_ECN)
+		    flags |= TH_ECE | TH_CWR;
+	}
+
+
+	/*  For easy checksum computing:
+	    saddr
+	    daddr
+	    length
+	    protocol
+	    tcphdr
+	    tcpoptions
+	*/
+
+	ptr = buf;
+
+	if (af == AF_INET) {
+	    len = sizeof (src.sin.sin_addr);
+	    memcpy (ptr, &src.sin.sin_addr, len);
+	    ptr += len;
+	    memcpy (ptr, &dest_addr.sin.sin_addr, len);
+	    ptr += len;
+	} else {
+	    len = sizeof (src.sin6.sin6_addr);
+	    memcpy (ptr, &src.sin6.sin6_addr, len);
+	    ptr += len;
+	    memcpy (ptr, &dest_addr.sin6.sin6_addr, len);
+	    ptr += len;
+	}
+
+	lenp = (uint16_t *) ptr;
+	ptr += sizeof (uint16_t);
+	*((uint16_t *) ptr) = htons ((uint16_t) IPPROTO_TCP);
+	ptr += sizeof (uint16_t);
+
+
+	/*  Construct TCP header   */
+
+	th = (struct tcphdr *) ptr;
+
+	th->source = 0;	    /*  temporary   */
+	th->dest = dest_port;
+	th->seq = 0;	    /*  temporary   */
+	th->ack_seq = 0;
+	th->doff = 0;	    /*  later...  */
+	TH_FLAGS(th) = flags & 0xff;
+	th->window = htons (4 * mtu);
+	th->check = 0;
+	th->urg_ptr = 0;
+
+
+	/*  Build TCP options   */
+
+	ptr = (uint8_t *) (th + 1);
+
+	if (flags & TH_SYN) {
+	    *ptr++ = TCPOPT_MAXSEG;	/*  2   */
+	    *ptr++ = TCPOLEN_MAXSEG;	/*  4   */
+	    *((uint16_t *) ptr) = htons (mss ? mss : (unsigned int)mtu);
+	    ptr += sizeof (uint16_t);
+	}
+
+	if (flags & FL_TSTAMP) {
+
+	    if (flags & FL_SACK) {
+		*ptr++ = TCPOPT_SACK_PERMITTED;	/*  4   */
+		*ptr++ = TCPOLEN_SACK_PERMITTED;/*  2   */
+	    } else {
+		*ptr++ = TCPOPT_NOP;	/*  1   */
+		*ptr++ = TCPOPT_NOP;	/*  1   */
+	    }
+	    *ptr++ = TCPOPT_TIMESTAMP;	/*  8   */
+	    *ptr++ = TCPOLEN_TIMESTAMP;	/*  10  */
+
+	    *((uint32_t *) ptr) = random_seq ();	/*  really!  */
+	    ptr += sizeof (uint32_t);
+	    *((uint32_t *) ptr) = (flags & TH_ACK) ? random_seq () : 0;
+	    ptr += sizeof (uint32_t);
+	}
+	else if (flags & FL_SACK) {
+	    *ptr++ = TCPOPT_NOP;	/*  1   */
+	    *ptr++ = TCPOPT_NOP;	/*  1   */
+	    *ptr++ = TCPOPT_SACK_PERMITTED;	/*  4   */
+	    *ptr++ = TCPOLEN_SACK_PERMITTED;	/*  2   */
+	}
+
+	if (flags & FL_WSCALE) {
+	    *ptr++ = TCPOPT_NOP;	/*  1   */
+	    *ptr++ = TCPOPT_WINDOW;	/*  3   */
+	    *ptr++ = TCPOLEN_WINDOW;	/*  3   */
+	    *ptr++ = 2;	/*  assume some corect value...  */
+	}
+
+
+	csum_len = ptr - buf;
+
+	if (csum_len > sizeof (buf))
+		error ("impossible");	/*  paranoia   */
+
+	len = ptr - (uint8_t *) th;
+	if (len & 0x03)  error ("impossible");	/*  as >>2 ...  */
+
+	*lenp = htons (len);
+	th->doff = len >> 2;
+
+
+	*packet_len_p = len;
+
+	return 0;
+}
+
+
+static void tcp_send_probe (probe *pb, int ttl) {
+	int sk;
+	int af = dest_addr.sa.sa_family;
+	sockaddr_any addr;
+	socklen_t len = sizeof (addr);
+
+
+	/*  To make sure we have chosen a free unused "source port",
+	   just create, (auto)bind and hold a socket while the port is needed.
+	*/
+
+	sk = socket (af, SOCK_STREAM, 0);
+	if (sk < 0)  error ("socket");
+
+	if (reuse && setsockopt (sk, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
+		error ("setsockopt SO_REUSEADDR");
+
+	bind_socket (sk);
+
+	if (getsockname (sk, &addr.sa, &len) < 0)
+		error ("getsockname");
+
+	/*  When we reach the target host, it can send us either RST or SYN+ACK.
+	  For RST all is OK (we and kernel just answer nothing), but
+	  for SYN+ACK we should reply with our RST.
+	    It is well-known "half-open technique", used by port scanners etc.
+	  This way we do not touch remote applications at all, unlike
+	  the ordinary connect(2) call.
+	    As the port-holding socket neither connect() nor listen(),
+	  it means "no such port yet" for remote ends, and kernel always
+	  send RST in such a situation automatically (we have to do nothing).
+	*/
+
+
+	th->source = addr.sin.sin_port;
+
+	th->seq = random_seq ();
+
+	th->check = 0;
+	th->check = in_csum (buf, csum_len);
+
+
+	if (ttl != last_ttl) {
+
+	    set_ttl (raw_sk, ttl);
+
+	    last_ttl = ttl;
+	}
+
+
+	pb->send_time = get_time ();
+
+	if (do_send (raw_sk, th, th->doff << 2, &dest_addr) < 0) {
+	    close (sk);
+	    pb->send_time = 0;
+	    return;
+	}
+
+
+	pb->seq = th->source;
+
+	pb->sk = sk;
+
+	return;
+}
+
+
+static probe *tcp_check_reply (int sk, int err, sockaddr_any *from,
+						    char *buf, size_t len) {
+    UNUSED(sk);
+	probe *pb;
+	struct tcphdr *tcp = (struct tcphdr *) buf;
+	uint16_t sport, dport;
+
+
+	if (len < 8)  return NULL;	    /*  too short   */
+
+
+	if (err) {
+	    sport = tcp->source;
+	    dport = tcp->dest;
+	} else {
+	    sport = tcp->dest;
+	    dport = tcp->source;
+	}
+
+
+	if (dport != dest_port)
+		return NULL;
+
+	if (!equal_addr (&dest_addr, from))
+		return NULL;
+
+	pb = probe_by_seq (sport);
+	if (!pb)  return NULL;
+
+
+	if (!err) {
+
+	    pb->final = 1;
+
+	    if (info)
+		pb->ext = names_by_flags (TH_FLAGS(tcp));
+	}
+
+	return pb;
+}
+
+
+static void tcp_recv_probe (int sk, int revents) {
+
+	if (!(revents & (POLLIN | POLLERR)))
+		return;
+
+	recv_reply (sk, !!(revents & POLLERR), tcp_check_reply);
+}
+
+
+static void tcp_expire_probe (probe *pb) {
+
+	probe_done (pb);
+}
+
+
+static tr_module tcp_ops = {
+	.name = "tcp",
+	.init = tcp_init,
+	.send_probe = tcp_send_probe,
+	.recv_probe = tcp_recv_probe,
+	.expire_probe = tcp_expire_probe,
+	.options = tcp_options,
+};
+
+TR_MODULE (tcp_ops);
+
+void tr_module_tcp_insert()
+{
+
+}
diff --git a/libdap-chain-net/iputils/traceroute/mod-tcpconn.c b/libdap-chain-net/iputils/traceroute/mod-tcpconn.c
new file mode 100644
index 0000000000000000000000000000000000000000..13e239864756de3639330189389933df570e8bf6
--- /dev/null
+++ b/libdap-chain-net/iputils/traceroute/mod-tcpconn.c
@@ -0,0 +1,237 @@
+/*
+    Copyright (c)  2006, 2007		Dmitry Butskoy
+					<buc@citadel.stu.neva.ru>
+    License:  GPL v2 or any later
+
+    See COPYING for the status of this software.
+*/
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <poll.h>
+#include <netinet/icmp6.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <netinet/tcp.h>
+#include <errno.h>
+
+#include "traceroute.h"
+
+
+static sockaddr_any dest_addr = {{ 0, }, };
+
+static int icmp_sk = -1;
+
+
+static int tcp_init (const sockaddr_any *dest,
+			    unsigned int port_seq, size_t *packet_len_p) {
+    UNUSED(packet_len_p);
+	int af = dest->sa.sa_family;
+
+	dest_addr = *dest;
+	dest_addr.sin.sin_port = htons (DEF_TCP_PORT);
+
+	if (port_seq)
+	    dest_addr.sin.sin_port = htons (port_seq);
+
+
+	/*  Currently an ICMP socket is the only way
+	  to obtain the needed info...
+	*/
+	icmp_sk = socket (af, SOCK_RAW, (af == AF_INET) ? IPPROTO_ICMP
+							: IPPROTO_ICMPV6);
+	if (icmp_sk < 0)
+		error_or_perm ("socket");
+
+	/*  icmp_sk not need full tune_socket() here, just a receiving one  */
+	bind_socket (icmp_sk);
+	use_timestamp (icmp_sk);
+	use_recv_ttl (icmp_sk);
+
+	add_poll (icmp_sk, POLLIN);
+
+	return 0;
+}
+
+
+static void tcp_send_probe (probe *pb, int ttl) {
+	int sk;
+	int af = dest_addr.sa.sa_family;
+	sockaddr_any addr;
+	socklen_t length = sizeof (addr);
+
+
+	sk = socket (af, SOCK_STREAM, 0);
+	if (sk < 0)  error ("socket");
+
+	tune_socket (sk);	/*  common stuff   */
+
+	set_ttl (sk, ttl);
+
+
+	pb->send_time = get_time ();
+
+	if (connect (sk, &dest_addr.sa, sizeof (dest_addr)) < 0) {
+	    if (errno != EINPROGRESS)
+		    error ("connect");
+	}
+
+
+	if (getsockname (sk, &addr.sa, &length) < 0)
+		error ("getsockname");
+
+	pb->seq = addr.sin.sin_port;	/*  both ipv4/ipv6  */
+
+	pb->sk = sk;
+
+	add_poll (sk, POLLERR | POLLHUP | POLLOUT);
+
+	return;
+}
+
+
+static probe *tcp_check_reply (int sk, int err, sockaddr_any *from,
+						    char *buf, size_t len) {
+    UNUSED(sk);
+    UNUSED(err);
+    UNUSED(from);
+	int af = dest_addr.sa.sa_family;
+	int type, code, info;
+	probe *pb;
+	struct tcphdr *tcp;
+
+
+	if (len < sizeof (struct icmphdr))
+		return NULL;
+
+
+	if (af == AF_INET) {
+	    struct icmp *icmp = (struct icmp *) buf;
+	    struct iphdr *ip;
+	    int hlen;
+
+	    type = icmp->icmp_type;
+	    code = icmp->icmp_code;
+	    info = icmp->icmp_void;
+
+	    if (type != ICMP_TIME_EXCEEDED && type != ICMP_DEST_UNREACH)
+		    return NULL;
+
+	    if (len < sizeof (struct icmphdr) + sizeof (struct iphdr) + 8)
+		    /* `8' - rfc1122: 3.2.2  */
+		    return NULL;
+
+	    ip = (struct iphdr *) (((char *)icmp) + sizeof(struct icmphdr));
+	    hlen = ip->ihl << 2;
+
+	    if (len < sizeof (struct icmphdr) + hlen + 8)
+		    return NULL;
+	    if (ip->protocol != IPPROTO_TCP)
+		    return NULL;
+
+	    tcp = (struct tcphdr *) (((char *) ip) + hlen);
+
+	}
+	else {	    /*  AF_INET6   */
+	    struct icmp6_hdr *icmp6 = (struct icmp6_hdr *) buf;
+	    struct ip6_hdr *ip6;
+
+	    type = icmp6->icmp6_type;
+	    code = icmp6->icmp6_code;
+	    info = icmp6->icmp6_mtu;
+
+	    if (type != ICMP6_TIME_EXCEEDED &&
+		type != ICMP6_DST_UNREACH &&
+		type != ICMP6_PACKET_TOO_BIG
+	    )  return NULL;
+
+	    if (len < sizeof(struct icmp6_hdr) + sizeof(struct ip6_hdr) + 8)
+		    return NULL;
+
+	    ip6 = (struct ip6_hdr *) (icmp6 + 1);
+	    if (ip6->ip6_nxt != IPPROTO_TCP)
+		    return NULL;
+
+	    tcp = (struct tcphdr *) (ip6 + 1);
+
+	}
+
+
+	if (tcp->dest != dest_addr.sin.sin_port)
+		return NULL;
+
+	pb = probe_by_seq (tcp->source);
+	if (!pb)  return NULL;
+
+
+	/*  here only, high level has no data to do this   */
+	parse_icmp_res (pb, type, code, info);
+
+	return pb;
+}
+
+
+static void tcp_recv_probe (int sk, int revents) {
+
+	if (sk != icmp_sk) {	/*  a tcp socket   */
+	    probe *pb;
+
+	    pb = probe_by_sk (sk);
+	    if (!pb) {
+		del_poll (sk);
+		return;
+	    }
+
+
+	    /*  do connect() again and check errno, regardless of revents  */
+	    if (connect (sk, &dest_addr.sa, sizeof (dest_addr)) < 0) {
+		if (errno != EISCONN && errno != ECONNREFUSED)
+			return;	/*  ICMP say more   */
+	    }
+
+	    /*  we have reached the dest host (either connected or refused)  */
+
+	    memcpy (&pb->res, &dest_addr, sizeof (pb->res));
+
+	    pb->final = 1;
+
+	    pb->recv_time = get_time ();
+
+	    probe_done (pb);
+
+	    return;
+	}
+
+
+	/*  ICMP stuff   */
+
+	if (!(revents & POLLIN))
+		return;
+
+	recv_reply (icmp_sk, 0, tcp_check_reply);
+}
+
+
+static void tcp_expire_probe (probe *pb) {
+
+	probe_done (pb);
+}
+
+
+static tr_module tcp_ops = {
+	.name = "tcpconn",
+	.init = tcp_init,
+	.send_probe = tcp_send_probe,
+	.recv_probe = tcp_recv_probe,
+	.expire_probe = tcp_expire_probe,
+};
+
+TR_MODULE (tcp_ops);
+
+void tr_module_tcpconn_insert()
+{
+
+}
diff --git a/libdap-chain-net/iputils/traceroute/mod-udp.c b/libdap-chain-net/iputils/traceroute/mod-udp.c
new file mode 100644
index 0000000000000000000000000000000000000000..e24d0271c8c6e60f40592f0be18e3e7c7b482379
--- /dev/null
+++ b/libdap-chain-net/iputils/traceroute/mod-udp.c
@@ -0,0 +1,241 @@
+/*
+    Copyright (c)  2006, 2007		Dmitry Butskoy
+					<buc@citadel.stu.neva.ru>
+    License:  GPL v2 or any later
+
+    See COPYING for the status of this software.
+*/
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <poll.h>
+#include <netinet/in.h>
+#include <netinet/udp.h>
+
+#include "traceroute.h"
+
+
+#ifndef IPPROTO_UDPLITE
+#define IPPROTO_UDPLITE	136
+#endif
+
+#ifndef UDPLITE_SEND_CSCOV
+#define UDPLITE_SEND_CSCOV	10
+#define UDPLITE_RECV_CSCOV	11
+#endif
+
+
+static sockaddr_any dest_addr = {{ 0, }, };
+static unsigned int curr_port = 0;
+static unsigned int protocol = IPPROTO_UDP;
+
+
+static char *data = NULL;
+static size_t *length_p;
+
+static void fill_data (size_t *packet_len_p) {
+	int i;
+
+	length_p = packet_len_p;
+
+	if (*length_p &&
+	    !(data = malloc (*length_p))
+	)  error ("malloc");
+
+        for (i = 0; i < (int)*length_p; i++)
+                data[i] = 0x40 + (i & 0x3f);
+ 
+	return;
+}
+
+
+static int udp_default_init (const sockaddr_any *dest,
+				unsigned int port_seq, size_t *packet_len_p) {
+
+	curr_port = port_seq ? port_seq : DEF_START_PORT;
+
+	dest_addr = *dest;
+	dest_addr.sin.sin_port = htons (curr_port);
+
+	fill_data (packet_len_p);
+
+	return 0;
+}
+
+
+static int udp_init (const sockaddr_any *dest,
+			    unsigned int port_seq, size_t *packet_len_p) {
+
+	dest_addr = *dest;
+
+	if (!port_seq)  port_seq = DEF_UDP_PORT;
+	dest_addr.sin.sin_port = htons ((uint16_t) port_seq);
+	
+	fill_data (packet_len_p);
+ 
+	return 0;
+}
+
+
+static unsigned int coverage = 0;
+#define MIN_COVERAGE	(sizeof (struct udphdr))
+
+static void set_coverage (int sk) {
+	int val = MIN_COVERAGE;
+
+	if (setsockopt (sk, IPPROTO_UDPLITE, UDPLITE_SEND_CSCOV,
+					    &coverage, sizeof (coverage)) < 0
+	)  error ("UDPLITE_SEND_CSCOV");
+
+	if (setsockopt (sk, IPPROTO_UDPLITE, UDPLITE_RECV_CSCOV,
+					    &val, sizeof (val)) < 0
+	)  error ("UDPLITE_RECV_CSCOV");
+}
+	
+static CLIF_option udplite_options[] = {
+	{ 0, "coverage", "NUM", "Set udplite send coverage to %s (default is "
+				_TEXT(MIN_COVERAGE) ")",
+				CLIF_set_uint, &coverage, 0, CLIF_ABBREV },
+	CLIF_END_OPTION
+};
+
+static int udplite_init (const sockaddr_any *dest,
+			    unsigned int port_seq, size_t *packet_len_p) {
+
+	dest_addr = *dest;
+
+	if (!port_seq)  port_seq = DEF_UDP_PORT;    /*  XXX: Hmmm...   */
+	dest_addr.sin.sin_port = htons ((uint16_t) port_seq);
+
+	protocol = IPPROTO_UDPLITE;
+
+	if (!coverage)  coverage = MIN_COVERAGE;
+	
+	fill_data (packet_len_p);
+ 
+	return 0;
+}
+
+
+static void udp_send_probe (probe *pb, int ttl) {
+	int sk;
+	int af = dest_addr.sa.sa_family;
+
+
+	sk = socket (af, SOCK_DGRAM, protocol);
+	if (sk < 0)  error ("socket");
+
+	tune_socket (sk);	/*  common stuff   */
+
+	if (coverage)  set_coverage (sk);	/*  udplite case   */
+
+	set_ttl (sk, ttl);
+
+
+	if (connect (sk, &dest_addr.sa, sizeof (dest_addr)) < 0)
+		error ("connect");
+
+	use_recverr (sk);
+
+
+	pb->send_time = get_time ();
+
+	if (do_send (sk, data, *length_p, NULL) < 0) {
+	    close (sk);
+	    pb->send_time = 0;
+	    return;
+	}
+
+
+	pb->sk = sk;
+
+	add_poll (sk, POLLIN | POLLERR);
+
+	pb->seq = dest_addr.sin.sin_port;
+
+	if (curr_port) {	/*  traditional udp method   */
+	    curr_port++;
+	    dest_addr.sin.sin_port = htons (curr_port);	/* both ipv4 and ipv6 */
+	}
+
+	return;
+}
+
+
+static probe *udp_check_reply (int sk, int err, sockaddr_any *from,
+						    char *buf, size_t len) {
+	UNUSED(buf);
+	UNUSED(len);
+    probe *pb;
+
+	pb = probe_by_sk (sk);
+	if (!pb)  return NULL;
+
+	if (pb->seq != from->sin.sin_port)
+		return NULL;
+
+	if (!err)  pb->final = 1;
+
+	return pb;
+}
+
+
+static void udp_recv_probe (int sk, int revents) {
+
+	if (!(revents & (POLLIN | POLLERR)))
+		return;
+
+	recv_reply (sk, !!(revents & POLLERR), udp_check_reply);
+}
+
+
+static void udp_expire_probe (probe *pb) {
+
+	probe_done (pb);
+}
+
+
+/*  All three modules share the same methods except the init...  */
+
+static tr_module default_ops = {
+	.name = "default",
+	.init = udp_default_init,
+	.send_probe = udp_send_probe,
+	.recv_probe = udp_recv_probe,
+	.expire_probe = udp_expire_probe,
+	.header_len = sizeof (struct udphdr),
+};
+
+TR_MODULE (default_ops);
+
+
+static tr_module udp_ops = {
+	.name = "udp",
+	.init = udp_init,
+	.send_probe = udp_send_probe,
+	.recv_probe = udp_recv_probe,
+	.expire_probe = udp_expire_probe,
+	.header_len = sizeof (struct udphdr),
+};
+
+TR_MODULE (udp_ops);
+
+
+static tr_module udplite_ops = {
+	.name = "udplite",
+	.init = udplite_init,
+	.send_probe = udp_send_probe,
+	.recv_probe = udp_recv_probe,
+	.expire_probe = udp_expire_probe,
+	.header_len = sizeof (struct udphdr),
+	.options = udplite_options,
+};
+
+TR_MODULE (udplite_ops);
+
+void tr_module_udp_insert()
+{
+
+}
diff --git a/libdap-chain-net/iputils/traceroute/module.c b/libdap-chain-net/iputils/traceroute/module.c
new file mode 100644
index 0000000000000000000000000000000000000000..f8f228ef635758a62b036a87986cdfae27f210bb
--- /dev/null
+++ b/libdap-chain-net/iputils/traceroute/module.c
@@ -0,0 +1,51 @@
+/*
+ Copyright (c)  2006, 2007		Dmitry Butskoy
+ <buc@citadel.stu.neva.ru>
+ License:  GPL v2 or any later
+
+ See COPYING for the status of this software.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+
+#include "traceroute.h"
+
+void tr_module_icmp_insert();
+void tr_module_udp_insert();
+void tr_module_tcp_insert();
+void tr_module_tcpconn_insert();
+void tr_module_raw_insert();
+void tr_module_dccp_insert();
+
+static tr_module *base = NULL;
+
+void tr_register_module(tr_module *ops) {
+
+    ops->next = base;
+    base = ops;
+//    printf("tr_register_module name=%s\n", ops->name);
+}
+
+const tr_module *tr_get_module(const char *name) {
+    const tr_module *ops;
+
+    tr_module_icmp_insert();
+    tr_module_udp_insert();
+    tr_module_tcp_insert();
+    tr_module_tcpconn_insert();
+    tr_module_raw_insert();
+    tr_module_dccp_insert();
+
+    if(!name)
+        return 0;
+
+    for(ops = base; ops; ops = ops->next) {
+        if(!strcasecmp(name, ops->name))
+            return ops;
+    }
+
+    return NULL;
+}
diff --git a/libdap-chain-net/iputils/traceroute/poll.c b/libdap-chain-net/iputils/traceroute/poll.c
new file mode 100644
index 0000000000000000000000000000000000000000..232a83395bc600ab54bddb1d8ea7c98e3ceb8526
--- /dev/null
+++ b/libdap-chain-net/iputils/traceroute/poll.c
@@ -0,0 +1,93 @@
+/*
+    Copyright (c)  2006, 2007		Dmitry Butskoy
+					<buc@citadel.stu.neva.ru>
+    License:  GPL v2 or any later
+
+    See COPYING for the status of this software.
+*/
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <poll.h>
+#include <errno.h>
+#include <math.h>
+
+#include "traceroute.h"
+
+
+static struct pollfd *pfd = NULL;
+static unsigned int num_polls = 0;
+
+
+void add_poll (int fd, int events) {
+	int i;
+
+	for (i = 0; i < (int)num_polls && pfd[i].fd > 0; i++) ;
+
+	if (i == (int)num_polls) {
+	    pfd = realloc (pfd, ++num_polls * sizeof (*pfd));
+	    if (!pfd)  error ("realloc");
+	}
+
+	pfd[i].fd = fd;
+	pfd[i].events = events;
+}
+
+
+void del_poll (int fd) {
+	int i;
+
+	for (i = 0; i < (int)num_polls && pfd[i].fd != fd; i++) ;
+
+	if (i < (int)num_polls)  pfd[i].fd = -1;    /*  or just zero it...  */
+}
+
+
+static int cleanup_polls (void) {
+	int i;
+
+	for (i = 0; i < (int)num_polls && pfd[i].fd > 0; i++) ;
+
+	if (i < (int)num_polls) {	/*  a hole have found   */
+	    int j;
+
+	    for (j = i + 1; j < (int)num_polls; j++) {
+		if (pfd[j].fd > 0) {
+		    pfd[i++] = pfd[j];
+		    pfd[j].fd = -1;
+		}
+	    }
+	}
+
+	return i;
+}
+
+
+void do_poll (double timeout, void (*callback) (int fd, int revents)) {
+	int nfds;
+	int msecs = ceil (timeout * 1000);
+
+	while ((nfds = cleanup_polls ()) > 0) {
+	    int i, n;
+
+	    n = poll (pfd, nfds, msecs);
+
+	    if (n <= 0) {
+		if (n == 0 || errno == EINTR)
+			return;
+		error ("poll");
+	    }
+
+	    for (i = 0; n && i < (int)num_polls; i++) {
+		if (pfd[i].revents) {
+		    callback (pfd[i].fd, pfd[i].revents);
+		    n--;
+		}
+	    }
+
+	    msecs = 0;	    /*  no more wait, just eat all the pending   */
+	}
+
+	return;
+}
+
diff --git a/libdap-chain-net/iputils/traceroute/random.c b/libdap-chain-net/iputils/traceroute/random.c
new file mode 100644
index 0000000000000000000000000000000000000000..5a8f911e1463b797559b263b5d9cd7081a82a428
--- /dev/null
+++ b/libdap-chain-net/iputils/traceroute/random.c
@@ -0,0 +1,28 @@
+/*
+    Copyright (c)  2006, 2007		Dmitry Butskoy
+					<buc@citadel.stu.neva.ru>
+    License:  GPL v2 or any later
+
+    See COPYING for the status of this software.
+*/
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/times.h>
+
+#include "traceroute.h"
+
+
+static void __init_random_seq (void) __attribute__ ((constructor));
+static void __init_random_seq (void) {
+
+	srand (times (NULL) + getpid ());
+}
+
+
+unsigned int random_seq (void) {
+
+	/*  To not worry about RANDOM_MAX and precision...  */
+	return  (rand () << 16) ^ (rand () << 8) ^ rand () ^ (rand () >> 8);
+}
+
diff --git a/libdap-chain-net/iputils/traceroute/time.c b/libdap-chain-net/iputils/traceroute/time.c
new file mode 100644
index 0000000000000000000000000000000000000000..16ea82483cc3483ada3a62c81c8436f820667191
--- /dev/null
+++ b/libdap-chain-net/iputils/traceroute/time.c
@@ -0,0 +1,27 @@
+/*
+    Copyright (c)  2006, 2007		Dmitry Butskoy
+					<buc@citadel.stu.neva.ru>
+    License:  GPL v2 or any later
+
+    See COPYING for the status of this software.
+*/
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/time.h>
+
+#include "traceroute.h"
+
+
+/*  Just returns current time as double, with most possible precision...  */
+
+double get_time (void) {
+	struct timeval tv;
+	double d;
+
+	gettimeofday (&tv, NULL);
+
+	d = ((double) tv.tv_usec) / 1000000. + (unsigned long) tv.tv_sec;
+
+	return d;
+}
diff --git a/libdap-chain-net/iputils/traceroute/traceroute.c b/libdap-chain-net/iputils/traceroute/traceroute.c
new file mode 100755
index 0000000000000000000000000000000000000000..a0dbeb2e233478034ab0b0fb6caec1a7285f74ed
--- /dev/null
+++ b/libdap-chain-net/iputils/traceroute/traceroute.c
@@ -0,0 +1,1758 @@
+/*
+ Copyright (c)  2006, 2007    Dmitry Butskoy
+ <buc@citadel.stu.neva.ru>
+ License:  GPL v2 or any later
+
+ See COPYING for the status of this software.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <poll.h>
+#include <netinet/icmp6.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/in.h>
+#include <netinet/ip6.h>
+#include <netdb.h>
+#include <errno.h>
+#include <locale.h>
+#include <sys/utsname.h>
+#include <linux/types.h>
+#include <linux/errqueue.h>
+
+/*  XXX: Remove this when things will be defined properly in netinet/ ...  */
+#include "flowlabel.h"
+
+//#include "version.h"
+#include <stdbool.h>
+//#include <glib.h>
+#include "traceroute.h"
+
+#ifndef ICMP6_DST_UNREACH_BEYONDSCOPE
+#ifdef ICMP6_DST_UNREACH_NOTNEIGHBOR
+#define ICMP6_DST_UNREACH_BEYONDSCOPE ICMP6_DST_UNREACH_NOTNEIGHBOR
+#else
+#define ICMP6_DST_UNREACH_BEYONDSCOPE 2
+#endif
+#endif
+
+#ifndef IPV6_RECVHOPLIMIT
+#define IPV6_RECVHOPLIMIT IPV6_HOPLIMIT
+#endif
+
+#ifndef IP_PMTUDISC_PROBE
+#define IP_PMTUDISC_PROBE 3
+#endif
+
+#ifndef IPV6_PMTUDISC_PROBE
+#define IPV6_PMTUDISC_PROBE 3
+#endif
+
+#ifndef AI_IDN
+#define AI_IDN  0
+#endif
+
+#ifndef NI_IDN
+#define NI_IDN  0
+#endif
+
+#define MAX_HOPS  255
+#define MAX_PROBES  10
+#define MAX_GATEWAYS_4  8
+#define MAX_GATEWAYS_6  127
+#define DEF_HOPS  30
+#define DEF_SIM_PROBES  16  /*  including several hops   */
+#define DEF_NUM_PROBES  3 //1
+#define DEF_WAIT_SECS 5.0
+#define DEF_HERE_FACTOR 3
+#define DEF_NEAR_FACTOR 10
+#ifndef DEF_WAIT_PREC
+#define DEF_WAIT_PREC 0.001 /*  +1 ms  to avoid precision issues   */
+#endif
+#define DEF_SEND_SECS 0
+#define DEF_DATA_LEN  40  /*  all but IP header...  */
+#define MAX_PACKET_LEN  65000
+#ifndef DEF_AF
+#define DEF_AF    AF_INET
+#endif
+
+#define ttl2hops(X) (((X) <= 64 ? 65 : ((X) <= 128 ? 129 : 256)) - (X))
+
+static char version_string[] = "Modern traceroute for Linux, "
+        "version " _TEXT(VERSION)
+"\nCopyright (c) 2016  Dmitry Butskoy, "
+"  License: GPL v2 or any later";
+static int debug = 0;
+static unsigned int first_hop = 1;
+static unsigned int max_hops = DEF_HOPS;
+static unsigned int sim_probes = DEF_SIM_PROBES;
+static unsigned int probes_per_hop = DEF_NUM_PROBES;
+
+static char **gateways = NULL;
+static int num_gateways = 0;
+static unsigned char *rtbuf = NULL;
+static size_t rtbuf_len = 0;
+static unsigned int ipv6_rthdr_type = 2; /*  IPV6_RTHDR_TYPE_2   */
+
+static size_t header_len = 0;
+static size_t data_len = 0;
+
+static int dontfrag = 0;
+static int noresolve = 0;
+static int extension = 0;
+static int as_lookups = 0;
+static unsigned int dst_port_seq = 0;
+static unsigned int tos = 0;
+static unsigned int flow_label = 0;
+static int noroute = 0;
+static unsigned int fwmark = 0;
+static int packet_len = -1;
+static double wait_secs = DEF_WAIT_SECS;
+static double here_factor = DEF_HERE_FACTOR;
+static double near_factor = DEF_NEAR_FACTOR;
+static double send_secs = DEF_SEND_SECS;
+static int mtudisc = 0;
+static int backward = 0;
+
+static sockaddr_any dst_addr = { { 0, }, };
+static char *dst_name = NULL;
+static char *device = NULL;
+static sockaddr_any src_addr = { { 0, }, };
+static unsigned int src_port = 0;
+
+static const char *module = "default";
+static const tr_module *ops = NULL;
+
+static char *opts[16] = { NULL, }; /*  assume enough   */
+static unsigned int opts_idx = 1; /*  first one reserved...   */
+
+static int af = 0;
+
+static probe *probes = NULL;
+static unsigned int num_probes = 0;
+
+static void ex_error(const char *format, ...) {
+    va_list ap;
+
+    va_start(ap, format);
+    vfprintf(stderr, format, ap);
+    va_end(ap);
+
+    fprintf(stderr, "\n");
+
+    //exit(2);
+}
+
+void error(const char *str) {
+
+    fprintf(stderr, "\n");
+
+    perror(str);
+
+    exit(1);
+}
+
+void error_or_perm(const char *str) {
+
+    if(errno == EPERM)
+        fprintf(stderr, "You do not have enough privileges to use "
+                "this traceroute method.");
+    error(str);
+}
+
+/*  Set initial parameters according to how we was called   */
+
+static void check_progname(const char *name) {
+    const char *p;
+    int l;
+
+    p = strrchr(name, '/');
+    if(p)
+        p++;
+    else
+        p = name;
+
+    l = strlen(p);
+    if(l <= 0)
+        return;
+    l--;
+
+    if(p[l] == '6')
+        af = AF_INET6;
+    else if(p[l] == '4')
+        af = AF_INET;
+
+    if(!strncmp(p, "tcp", 3))
+        module = "tcp";
+    if(!strncmp(p, "tracert", 7))
+        module = "icmp";
+
+    return;
+}
+
+static int getaddr(const char *name, sockaddr_any *addr) {
+    int ret;
+    struct addrinfo hints, *ai, *res = NULL;
+
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_family = af;
+    hints.ai_flags = AI_IDN;
+
+    ret = getaddrinfo(name, NULL, &hints, &res);
+    if(ret) {
+        fprintf(stderr, "%s: %s\n", name, gai_strerror(ret));
+        return -1;
+    }
+
+    for(ai = res; ai; ai = ai->ai_next) {
+        if(ai->ai_family == af)
+            break;
+        /*  when af not specified, choose DEF_AF if present   */
+        if(!af && ai->ai_family == DEF_AF)
+            break;
+    }
+    if(!ai)
+        ai = res; /*  anything...  */
+
+    if(ai->ai_addrlen > sizeof(*addr))
+        return -1; /*  paranoia   */
+    memcpy(addr, ai->ai_addr, ai->ai_addrlen);
+
+    freeaddrinfo(res);
+
+    return 0;
+}
+
+static void make_fd_used(int fd) {
+    int nfd;
+
+    if(fcntl(fd, F_GETFL) != -1)
+        return;
+
+    if(errno != EBADF)
+        error("fcntl F_GETFL");
+
+    nfd = open("/dev/null", O_RDONLY);
+    if(nfd < 0)
+        error("open /dev/null");
+
+    if(nfd != fd) {
+        dup2(nfd, fd);
+        close(nfd);
+    }
+
+    return;
+}
+
+static char addr2str_buf[INET6_ADDRSTRLEN];
+
+static const char *addr2str(const sockaddr_any *addr) {
+
+    getnameinfo(&addr->sa, sizeof(*addr),
+            addr2str_buf, sizeof(addr2str_buf), 0, 0, NI_NUMERICHOST);
+
+    return addr2str_buf;
+}
+
+/*  IP  options  stuff      */
+
+static int init_ip_options(void) {
+    sockaddr_any *gates;
+    int i, max;
+
+    if(!num_gateways)
+        return 0;
+
+    /*  check for TYPE,ADDR,ADDR... form   */
+    if(af == AF_INET6 && num_gateways > 1 && gateways[0]) {
+        char *q;
+        unsigned int value = strtoul(gateways[0], &q, 0);
+
+        if(!*q) {
+            ipv6_rthdr_type = value;
+            num_gateways--;
+            for(i = 0; i < num_gateways; i++)
+                gateways[i] = gateways[i + 1];
+        }
+    }
+
+    max = af == AF_INET ? MAX_GATEWAYS_4 : MAX_GATEWAYS_6;
+    if(num_gateways > max) {
+        ex_error("Too many gateways specified. No more than %d", max);
+        return -1;
+    }
+
+    gates = alloca(num_gateways * sizeof(*gates));
+
+    for(i = 0; i < num_gateways; i++) {
+
+        if(!gateways[i])
+            error("strdup");
+
+        if(getaddr(gateways[i], &gates[i]) < 0) {
+            ex_error(""); /*  already reported   */
+            return -1;
+        }
+        if(gates[i].sa.sa_family != af) {
+            ex_error("IP versions mismatch in gateway addresses");
+            return -1;
+        }
+
+        free(gateways[i]);
+    }
+
+    free(gateways);
+    gateways = NULL;
+
+    if(af == AF_INET) {
+        struct in_addr *in;
+
+        rtbuf_len = 4 + (num_gateways + 1) * sizeof(*in);
+        rtbuf = malloc(rtbuf_len);
+        if(!rtbuf)
+            error("malloc");
+
+        in = (struct in_addr *) &rtbuf[4];
+        for(i = 0; i < num_gateways; i++)
+            memcpy(&in[i], &gates[i].sin.sin_addr, sizeof(*in));
+        /*  final hop   */
+        memcpy(&in[i], &dst_addr.sin.sin_addr, sizeof(*in));
+        i++;
+
+        rtbuf[0] = IPOPT_NOP;
+        rtbuf[1] = IPOPT_LSRR;
+        rtbuf[2] = (i * sizeof(*in)) + 3;
+        rtbuf[3] = IPOPT_MINOFF;
+
+    }
+    else if(af == AF_INET6) {
+        struct in6_addr *in6;
+        struct ip6_rthdr *rth;
+
+        /*  IPV6_RTHDR_TYPE_0 length is 8   */
+        rtbuf_len = 8 + num_gateways * sizeof(*in6);
+        rtbuf = malloc(rtbuf_len);
+        if(!rtbuf)
+            error("malloc");
+
+        rth = (struct ip6_rthdr *) rtbuf;
+        rth->ip6r_nxt = 0;
+        rth->ip6r_len = 2 * num_gateways;
+        rth->ip6r_type = ipv6_rthdr_type;
+        rth->ip6r_segleft = num_gateways;
+
+        *((uint32_t *) (rth + 1)) = 0;
+
+        in6 = (struct in6_addr *) (rtbuf + 8);
+        for(i = 0; i < num_gateways; i++)
+            memcpy(&in6[i], &gates[i].sin6.sin6_addr, sizeof(*in6));
+    }
+
+    return 0;
+}
+
+/*  Command line stuff      */
+__attribute__((unused))
+static int set_af(CLIF_option *optn, char *arg) {
+    UNUSED(arg);
+    int vers = (long) optn->data;
+
+    if(vers == 4)
+        af = AF_INET;
+    else if(vers == 6)
+        af = AF_INET6;
+    else
+        return -1;
+
+    return 0;
+}
+
+static int add_gateway(CLIF_option *optn, char *arg) {
+    UNUSED(optn);
+    if(num_gateways >= MAX_GATEWAYS_6) { /*  127 > 8 ... :)   */
+        fprintf(stderr, "Too many gateways specified.");
+        return -1;
+    }
+
+    gateways = realloc(gateways, (num_gateways + 1) * sizeof(*gateways));
+    if(!gateways)
+        error("malloc");
+    gateways[num_gateways++] = strdup(arg);
+
+    return 0;
+}
+
+static int set_source(CLIF_option *optn, char *arg) {
+    UNUSED(optn);
+    return getaddr(arg, &src_addr);
+}
+
+static int set_port(CLIF_option *optn, char *arg) {
+    unsigned int *up = (unsigned int *) optn->data;
+    char *q;
+
+    *up = strtoul(arg, &q, 0);
+    if(q == arg) {
+        struct servent *s = getservbyname(arg, NULL);
+
+        if(!s)
+            return -1;
+        *up = ntohs(s->s_port);
+    }
+
+    return 0;
+}
+
+static int set_module(CLIF_option *optn, char *arg) {
+    UNUSED(arg);
+    module = (char *) optn->data;
+
+    return 0;
+}
+
+static int set_mod_option(CLIF_option *optn, char *arg) {
+    UNUSED(optn);
+    if(!strcmp(arg, "help")) {
+        const tr_module *mod = tr_get_module(module);
+
+        if(mod && mod->options) {
+            /*  just to set common keyword flag...  */
+            CLIF_parse(1, &arg, 0, 0, CLIF_KEYWORD);
+            CLIF_print_options(NULL, mod->options);
+        } else
+            fprintf(stderr, "No options for module `%s'\n", module);
+
+        exit(0);
+    }
+
+    if(opts_idx >= sizeof(opts) / sizeof(*opts)) {
+        fprintf(stderr, "Too many module options\n");
+        return -1;
+    }
+
+    opts[opts_idx] = strdup(arg);
+    if(!opts[opts_idx])
+        error("strdup");
+    opts_idx++;
+
+    return 0;
+}
+
+static int set_raw(CLIF_option *optn, char *arg) {
+    char buf[1024];
+
+    module = "raw";
+
+    snprintf(buf, sizeof(buf), "protocol=%s", arg);
+    return set_mod_option(optn, buf);
+}
+
+static int set_wait_specs(CLIF_option *optn, char *arg) {
+    UNUSED(optn);
+    char *p, *q;
+
+    here_factor = near_factor = 0;
+
+    wait_secs = strtod(p = arg, &q);
+    if(q == p)
+        return -1;
+    if(!*q++)
+        return 0;
+
+    here_factor = strtod(p = q, &q);
+    if(q == p)
+        return -1;
+    if(!*q++)
+        return 0;
+
+    near_factor = strtod(p = q, &q);
+    if(q == p || *q)
+        return -1;
+
+    return 0;
+}
+
+static int set_host(CLIF_argument *argm, char *arg, int index) {
+    UNUSED(argm);
+    UNUSED(index);
+    if(getaddr(arg, &dst_addr) < 0)
+        return -1;
+
+    dst_name = arg;
+
+    /*  i.e., guess it by the addr in cmdline...  */
+    if(!af)
+        af = dst_addr.sa.sa_family;
+
+    return 0;
+}
+
+static CLIF_option option_list[] = {
+    { "4", 0, 0, "Use IPv4", set_af, (void *) 4, 0, CLIF_EXTRA },
+    { "6", 0, 0, "Use IPv6", set_af, (void *) 6, 0, 0 },
+    { "d", "debug", 0, "Enable socket level debugging",
+        CLIF_set_flag, &debug, 0, 0 },
+    { "F", "dont-fragment", 0, "Do not fragment packets",
+        CLIF_set_flag, &dontfrag, 0, CLIF_ABBREV },
+    { "f", "first", "first_ttl", "Start from the %s hop (instead from 1)",
+        CLIF_set_uint, &first_hop, 0, 0 },
+    { "g", "gateway", "gate", "Route packets through the specified gateway "
+            "(maximum " _TEXT(MAX_GATEWAYS_4) " for IPv4 and "
+    _TEXT(MAX_GATEWAYS_6) " for IPv6)",
+        add_gateway, 0, 0, CLIF_SEVERAL },
+    { "I", "icmp", 0, "Use ICMP ECHO for tracerouting",
+        set_module, "icmp", 0, 0 },
+    { "T", "tcp", 0, "Use TCP SYN for tracerouting (default "
+            "port is " _TEXT(DEF_TCP_PORT) ")",
+        set_module, "tcp", 0, 0 },
+    { "i", "interface", "device", "Specify a network interface "
+            "to operate with",
+        CLIF_set_string, &device, 0, 0 },
+    { "m", "max-hops", "max_ttl", "Set the max number of hops (max TTL "
+            "to be reached). Default is " _TEXT(DEF_HOPS),
+        CLIF_set_uint, &max_hops, 0, 0 },
+    { "N", "sim-queries", "squeries", "Set the number of probes "
+            "to be tried simultaneously (default is "
+    _TEXT(DEF_SIM_PROBES) ")",
+        CLIF_set_uint, &sim_probes, 0, 0 },
+    { "n", 0, 0, "Do not resolve IP addresses to their domain names",
+        CLIF_set_flag, &noresolve, 0, 0 },
+    { "p", "port", "port", "Set the destination port to use. "
+            "It is either initial udp port value for "
+            "\"default\" method (incremented by each probe, "
+            "default is " _TEXT(DEF_START_PORT) "), "
+    "or initial seq for \"icmp\" (incremented as well, "
+    "default from 1), or some constant destination port"
+    " for other methods (with default of "
+    _TEXT(DEF_TCP_PORT) " for \"tcp\", "
+    _TEXT(DEF_UDP_PORT) " for \"udp\", etc.)",
+        set_port, &dst_port_seq, 0, 0 },
+    { "t", "tos", "tos", "Set the TOS (IPv4 type of service) or TC "
+            "(IPv6 traffic class) value for outgoing packets",
+        CLIF_set_uint, &tos, 0, 0 },
+    { "l", "flowlabel", "flow_label", "Use specified %s for IPv6 packets",
+        CLIF_set_uint, &flow_label, 0, 0 },
+    { "w", "wait", "MAX,HERE,NEAR", "Wait for a probe no more than HERE "
+            "(default " _TEXT(DEF_HERE_FACTOR) ") times longer "
+    "than a response from the same hop, or no more "
+    "than NEAR (default " _TEXT(DEF_NEAR_FACTOR) ") "
+    "times than some next hop, or MAX (default "
+    _TEXT(DEF_WAIT_SECS) ") seconds "
+    "(float point values allowed too)",
+        set_wait_specs, 0, 0, 0 },
+    { "q", "queries", "nqueries", "Set the number of probes per each hop. "
+            "Default is " _TEXT(DEF_NUM_PROBES),
+        CLIF_set_uint, &probes_per_hop, 0, 0 },
+    { "r", 0, 0, "Bypass the normal routing and send directly to a host "
+            "on an attached network",
+        CLIF_set_flag, &noroute, 0, 0 },
+    { "s", "source", "src_addr", "Use source %s for outgoing packets",
+        set_source, 0, 0, 0 },
+    { "z", "sendwait", "sendwait", "Minimal time interval between probes "
+            "(default " _TEXT(DEF_SEND_SECS) "). If the value "
+    "is more than 10, then it specifies a number "
+    "in milliseconds, else it is a number of seconds "
+    "(float point values allowed too)",
+        CLIF_set_double, &send_secs, 0, 0 },
+    { "e", "extensions", 0, "Show ICMP extensions (if present), "
+            "including MPLS",
+        CLIF_set_flag, &extension, 0, CLIF_ABBREV },
+    { "A", "as-path-lookups", 0, "Perform AS path lookups in routing "
+            "registries and print results directly after "
+            "the corresponding addresses",
+        CLIF_set_flag, &as_lookups, 0, 0 },
+    { "M", "module", "name", "Use specified module (either builtin or "
+            "external) for traceroute operations. Most methods "
+            "have their shortcuts (`-I' means `-M icmp' etc.)",
+        CLIF_set_string, &module, 0, CLIF_EXTRA },
+    { "O", "options", "OPTS", "Use module-specific option %s for the "
+            "traceroute module. Several %s allowed, separated "
+            "by comma. If %s is \"help\", print info about "
+            "available options",
+        set_mod_option, 0, 0, CLIF_SEVERAL | CLIF_EXTRA },
+    { 0, "sport", "num", "Use source port %s for outgoing packets. "
+            "Implies `-N 1'",
+        set_port, &src_port, 0, CLIF_EXTRA },
+    #ifdef SO_MARK
+    { 0, "fwmark", "num", "Set firewall mark for outgoing packets",
+        CLIF_set_uint, &fwmark, 0, 0 },
+    #endif
+    { "U", "udp", 0, "Use UDP to particular port for tracerouting "
+            "(instead of increasing the port per each probe), "
+            "default port is " _TEXT(DEF_UDP_PORT),
+        set_module, "udp", 0, CLIF_EXTRA },
+    { 0, "UL", 0, "Use UDPLITE for tracerouting (default dest port is "
+    _TEXT(DEF_UDP_PORT) ")",
+        set_module, "udplite", 0, CLIF_ONEDASH | CLIF_EXTRA },
+    { "D", "dccp", 0, "Use DCCP Request for tracerouting (default "
+            "port is " _TEXT(DEF_DCCP_PORT) ")",
+        set_module, "dccp", 0, CLIF_EXTRA },
+    { "P", "protocol", "prot", "Use raw packet of protocol %s "
+            "for tracerouting",
+        set_raw, 0, 0, CLIF_EXTRA },
+    { 0, "mtu", 0, "Discover MTU along the path being traced. "
+            "Implies `-F -N 1'",
+        CLIF_set_flag, &mtudisc, 0, CLIF_EXTRA },
+    { 0, "back", 0, "Guess the number of hops in the backward path "
+            "and print if it differs",
+        CLIF_set_flag, &backward, 0, CLIF_EXTRA },
+    CLIF_VERSION_OPTION (version_string),
+    CLIF_HELP_OPTION,
+    CLIF_END_OPTION
+    };
+
+static CLIF_argument arg_list[] = {
+    { "host", "The host to traceroute to",
+        set_host, 0, CLIF_STRICT },
+    { "packetlen", "The full packet length (default is the length of "
+            "an IP header plus " _TEXT(DEF_DATA_LEN) "). Can be "
+    "ignored or increased to a minimal allowed value",
+        CLIF_arg_int, &packet_len, 0 },
+    CLIF_END_ARGUMENT
+    };
+
+static void do_it(void);
+
+int traceroute_main(int argc, char *argv[]) {
+
+    setlocale(LC_ALL, "");
+    setlocale(LC_NUMERIC, "C"); /*  avoid commas in msec printed  */
+
+    check_progname(argv[0]);
+
+    if(CLIF_parse(argc, argv, option_list, arg_list,
+    CLIF_MAY_JOIN_ARG | CLIF_HELP_EMPTY) < 0
+            )
+        return -EADDRNOTAVAIL; //exit(2);
+
+    ops = tr_get_module(module);
+    if(!ops) {
+        ex_error("Unknown traceroute module %s", module);
+        return -2;
+    }
+
+    if(!first_hop || first_hop > max_hops) {
+        ex_error("first hop out of range");
+        return -3;
+    }
+    if(max_hops > MAX_HOPS) {
+        ex_error("max hops cannot be more than " _TEXT(MAX_HOPS));
+        return -4;
+    }
+    if(!probes_per_hop || probes_per_hop > MAX_PROBES) {
+        ex_error("no more than " _TEXT(MAX_PROBES) " probes per hop");
+        return -5;
+    }
+    if(wait_secs < 0 || here_factor < 0 || near_factor < 0) {
+        ex_error("bad wait specifications `%g,%g,%g' used",
+                wait_secs, here_factor, near_factor);
+        return -6;
+    }
+    if(packet_len > MAX_PACKET_LEN) {
+        ex_error("too big packetlen %d specified", packet_len);
+        return -7;
+    }
+    if(src_addr.sa.sa_family && src_addr.sa.sa_family != af) {
+        ex_error("IP version mismatch in addresses specified");
+        return -8;
+    }
+    if(send_secs < 0) {
+        ex_error("bad sendtime `%g' specified", send_secs);
+        return -9;
+    }
+    if(send_secs >= 10) /*  it is milliseconds   */
+        send_secs /= 1000;
+
+    if(af == AF_INET6 && (tos || flow_label))
+        dst_addr.sin6.sin6_flowinfo =
+                htonl(((tos & 0xff) << 20) | (flow_label & 0x000fffff));
+
+    if(src_port) {
+        src_addr.sin.sin_port = htons((uint16_t) src_port);
+        src_addr.sa.sa_family = af;
+    }
+
+    if(src_port || ops->one_per_time) {
+        sim_probes = 1;
+        here_factor = near_factor = 0;
+    }
+
+    /*  make sure we don't std{in,out,err} to open sockets  */
+    make_fd_used(0);
+    make_fd_used(1);
+    make_fd_used(2);
+
+    if(init_ip_options() == -1)
+        return -10;
+
+    header_len = (af == AF_INET ? sizeof(struct iphdr)
+                                  :
+                                  sizeof(struct ip6_hdr)) +
+            rtbuf_len + ops->header_len;
+
+    if(mtudisc) {
+        dontfrag = 1;
+        sim_probes = 1;
+        if(packet_len < 0)
+            packet_len = MAX_PACKET_LEN;
+    }
+
+    if(packet_len < 0) {
+        if(DEF_DATA_LEN >= ops->header_len)
+            data_len = DEF_DATA_LEN - ops->header_len;
+    } else {
+        if(packet_len >= (int) header_len)
+            data_len = packet_len - header_len;
+    }
+
+    num_probes = max_hops * probes_per_hop;
+    probes = calloc(num_probes, sizeof(*probes));
+    if(!probes) {
+        error("calloc");
+        return -11;
+    }
+
+    if(ops->options && opts_idx > 1) {
+        opts[0] = strdup(module); /*  aka argv[0] ...  */
+        if(CLIF_parse(opts_idx, opts, ops->options, 0, CLIF_KEYWORD) < 0)
+            return -12; //exit(2);
+    }
+
+    if(ops->init(&dst_addr, dst_port_seq, &data_len) < 0) {
+        ex_error("trace method's init failed");
+        return -13;
+    }
+
+    do_it();
+    return 0;
+}
+
+/*  PRINT  STUFF      */
+
+static void print_header(void) {
+
+    /*  Note, without ending new-line!  */
+    log_printf("traceroute to %s (%s), %u hops max, %zu byte packets",
+            dst_name, addr2str(&dst_addr), max_hops,
+            header_len + data_len);
+    fflush(stdout);
+}
+
+static bool print_addr(sockaddr_any *res) {
+    bool is_final = false;
+    const char *str;
+
+    if(!res->sa.sa_family)
+        return is_final;
+
+    str = addr2str(res);
+
+    if(noresolve) {
+        log_printf("%s", str);
+        if(str && !strcmp(dst_name, str))
+            is_final = true;
+    }
+    else {
+        char buf[1024];
+
+        buf[0] = '\0';
+        getnameinfo(&res->sa, sizeof(*res), buf, sizeof(buf),
+                0, 0, NI_IDN);
+        log_printf(" %s (%s)", buf[0] ? buf : str, str);
+        if(buf[0] && !strcmp(dst_name, buf))
+            is_final = true;
+        else
+        if(str && !strcmp(dst_name, str))
+            is_final = true;
+    }
+
+    if(as_lookups)
+        log_printf(" [%s]", get_as_path(str));
+
+    return is_final;
+}
+
+static bool print_probe(probe *pb) {
+    bool is_final = false;
+    unsigned int idx = (pb - probes);
+    unsigned int ttl = idx / probes_per_hop + 1;
+    unsigned int np = idx % probes_per_hop;
+
+    if(np == 0)
+        log_printf("\n%2u ", ttl);
+
+    if(!pb->res.sa.sa_family)
+        log_printf(" *");
+    else {
+        int prn = !np; /*  print if the first...  */
+
+        if(np) { /*  ...and if differs with previous   */
+            probe *p;
+
+            /*  skip expired   */
+            for(p = pb - 1; np && !p->res.sa.sa_family; p--, np--)
+                ;
+
+            if(!np ||
+                    !equal_addr(&p->res, &pb->res) ||
+                    (p->ext != pb->ext &&
+                            !(p->ext && pb->ext && !strcmp(p->ext, pb->ext))) ||
+                    (backward && p->recv_ttl != pb->recv_ttl)
+                    )
+                prn = 1;
+        }
+
+        if(prn) {
+            is_final = print_addr(&pb->res);
+            if(pb->ext)
+                log_printf(" <%s>", pb->ext);
+
+            if(backward && pb->recv_ttl) {
+                int hops = ttl2hops(pb->recv_ttl);
+                if(hops != (int) ttl)
+                    log_printf(" '-%d'", hops);
+            }
+        }
+    }
+
+    if(pb->recv_time) {
+        double diff = pb->recv_time - pb->send_time;
+
+        log_printf("  %.3f ms", diff * 1000);
+    }
+
+    if(pb->err_str[0])
+        log_printf(" %s", pb->err_str);
+
+    fflush(stdout);
+
+    return is_final;
+}
+
+static void print_end(void) {
+
+    log_printf("\n");
+}
+
+/*  Compute  timeout  stuff   */
+
+static double get_timeout(probe *pb) {
+    double value;
+
+    if(here_factor) {
+        /*  check for already replied from the same hop   */
+        int i, idx = (pb - probes);
+        probe *p = &probes[idx - (idx % probes_per_hop)];
+
+        for(i = 0; i < (int) probes_per_hop; i++, p++) {
+            /*   `p == pb' skipped since  !pb->done   */
+
+            if(p->done && (value = p->recv_time - p->send_time) > 0) {
+                value += DEF_WAIT_PREC;
+                value *= here_factor;
+                return value < wait_secs ? value : wait_secs;
+            }
+        }
+    }
+
+    if(near_factor) {
+        /*  check forward for already replied   */
+        probe *p, *endp = probes + num_probes;
+
+        for(p = pb + 1; p < endp && p->send_time; p++) {
+
+            if(p->done && (value = p->recv_time - p->send_time) > 0) {
+                value += DEF_WAIT_PREC;
+                value *= near_factor;
+                return value < wait_secs ? value : wait_secs;
+            }
+        }
+    }
+
+    return wait_secs;
+}
+
+/*  Check  expiration  stuff  */
+
+static void check_expired(probe *pb) {
+    int idx = (pb - probes);
+    probe *p, *endp = probes + num_probes;
+    probe *fp = NULL, *pfp = NULL;
+
+    if(!pb->done) /*  an ops method still not release it  */
+        return;
+
+    /*  check all the previous in the same hop   */
+    for(p = &probes[idx - (idx % probes_per_hop)]; p < pb; p++) {
+
+        if(!p->done || /*  too early to decide something  */
+        !p->final /*  already ttl-exceeded in the same hop  */
+        )
+            return;
+
+        pfp = p; /*  some of the previous probes is final   */
+    }
+
+    /*  check forward all the sent probes   */
+    for(p = pb + 1; p < endp && p->send_time; p++) {
+
+        if(p->done) { /*  some next probe already done...  */
+            if(!p->final) /*  ...was ttl-exceeded. OK, we are expired.  */
+                return;
+            else {
+                fp = p;
+                break;
+            }
+        }
+    }
+
+    if(!fp) /*  no any final probe found. Assume expired.   */
+        return;
+
+    /*  Well. There is a situation "*(this) * * * * ... * * final"
+     We cannot guarantee that "final" is in its right place.
+     We've sent "sim_probes" simultaneously, and the final hop
+     can drop some of them and answer only for latest ones.
+     If we can detect/assume that it so, then just put "final"
+     to the (pseudo-expired) "this" place.
+     */
+
+    /*  It seems that the case of "answers for latest ones only"
+     occurs mostly with icmp_unreach error answers ("!H" etc.).
+     Icmp_echoreply, tcp_reset and even icmp_port_unreach looks
+     like going in the right order.
+     */
+    if(!fp->err_str[0]) /*  not an icmp_unreach error report...  */
+        return;
+
+    if(pfp ||
+            (idx % probes_per_hop) + (fp - pb) < probes_per_hop
+                    ) {
+        /*  Either some previous (pfp) or some next probe
+         in this hop is final. It means that the whole hop is final.
+         Do the replace (it also causes further "final"s to be shifted
+         here too).
+         */
+        goto replace_by_final;
+    }
+
+    /*  If the final probe is an icmp_unreachable report
+     (either in a case of some error, like "!H", or just port_unreach),
+     it could follow the "time-exceed" report from the *same* hop.
+     */
+    for(p = pb - 1; p >= probes; p--) {
+        if(equal_addr(&p->res, &fp->res)) {
+            /*  ...Yes. Put "final" to the "this" place.  */
+            goto replace_by_final;
+        }
+    }
+
+    if(fp->recv_ttl) {
+        /*  Consider the ttl value of the report packet and guess where
+         the "final" should be. If it seems that it should be
+         in the same hop as "this", then do replace.
+         */
+        int back_hops, ttl;
+
+        /*  We assume that the reporting one has an initial ttl value
+         of either 64, or 128, or 255. It is most widely used
+         in the modern routers and computers.
+         The idea comes from tracepath(1) routine.
+         */
+        back_hops = ttl2hops(fp->recv_ttl);
+
+        /*  It is possible that the back path differs from the forward
+         and therefore has different number of hops. To minimize
+         such an influence, get the nearest previous time-exceeded
+         probe and compare with it.
+         */
+        for(p = pb - 1; p >= probes; p--) {
+            if(p->done && !p->final && p->recv_ttl) {
+                int hops = ttl2hops(p->recv_ttl);
+
+                if(hops < back_hops) {
+                    ttl = (p - probes) / probes_per_hop + 1;
+                    back_hops = (back_hops - hops) + ttl;
+                    break;
+                }
+            }
+        }
+
+        ttl = idx / probes_per_hop + 1;
+        if(back_hops == ttl)
+            /*  Yes! It seems that "final" should be at "this" place   */
+            goto replace_by_final;
+        else if(back_hops < ttl)
+            /*  Hmmm... Assume better to replace here too...  */
+            goto replace_by_final;
+
+    }
+
+    /*  No idea what to do. Assume expired.  */
+
+    return;
+
+    replace_by_final:
+
+    *pb = *fp;
+
+    memset(fp, 0, sizeof(*fp));
+    /*  block extra re-send  */
+    fp->send_time = 1.;
+
+    return;
+}
+
+probe *probe_by_seq(int seq) {
+    int n;
+
+    if(seq <= 0)
+        return NULL;
+
+    for(n = 0; n < (int) num_probes; n++) {
+        if(probes[n].seq == seq)
+            return &probes[n];
+    }
+
+    return NULL;
+}
+
+probe *probe_by_sk(int sk) {
+    int n;
+
+    if(sk <= 0)
+        return NULL;
+
+    for(n = 0; n < (int) num_probes; n++) {
+        if(probes[n].sk == sk)
+            return &probes[n];
+    }
+
+    return NULL;
+}
+
+static void poll_callback(int fd, int revents) {
+
+    ops->recv_probe(fd, revents);
+}
+
+static void do_it(void) {
+    int start = (first_hop - 1) * probes_per_hop;
+    int end = num_probes;
+    double last_send = 0;
+
+    print_header();
+
+    while(start < end) {
+        int n, num = 0;
+        double next_time = 0;
+        double now_time = get_time();
+
+        for(n = start; n < end; n++) {
+            probe *pb = &probes[n];
+
+            if(n == start && /*  probably time to print...  */
+            !pb->done && pb->send_time /*  ...but yet not replied   */
+            ) {
+                double expire_time = pb->send_time + get_timeout(pb);
+
+                if(expire_time > now_time)
+                    next_time = expire_time;
+                else {
+                    ops->expire_probe(pb);
+                    check_expired(pb);
+                }
+            }
+
+            if(pb->done) {
+
+                if(n == start) { /*  can print it now   */
+                    if(print_probe(pb)) {
+                        pb->final = 1;
+                    }
+                    //log_printf("(host=%s,%s recv_ttl=%d)", dst_name, (host) ? host : "-", pb->recv_ttl);
+                    start++;
+                }
+
+                /*                {
+                 char buf[1024];
+                 buf[0] = '\0';
+                 getnameinfo(&(pb->res.sa), sizeof((pb->res)), buf, sizeof(buf),0, 0, NI_IDN);
+                 if(buf[0] && !strcmp(dst_name, buf))
+                 pb->real_final = true;
+                 //else
+                 //if(str && !strcmp(dst_name, str))
+                 //  pb->real_final = true;
+                 }*/
+
+                if(pb->final)
+                    end = (n / probes_per_hop + 1) * probes_per_hop;
+
+                continue;
+            }
+
+            if(!pb->send_time) {
+                int ttl;
+                double next;
+
+                if(send_secs && (next = last_send + send_secs) > now_time) {
+                    next_time = next;
+                    break;
+                }
+
+                ttl = n / probes_per_hop + 1;
+
+                ops->send_probe(pb, ttl);
+
+                if(!pb->send_time) {
+                    if(next_time)
+                        break; /*  have chances later   */
+                    else
+                        error("send probe");
+                }
+
+                last_send = pb->send_time;
+            }
+
+            if(!next_time)
+                next_time = pb->send_time + get_timeout(pb);
+
+            num++;
+            if(num >= (int) sim_probes)
+                break;
+        }
+
+        if(next_time) {
+            double timeout = next_time - get_time();
+
+            if(timeout < 0)
+                timeout = 0;
+
+            do_poll(timeout, poll_callback);
+        }
+
+    }
+
+    print_end();
+
+    return;
+}
+
+void tune_socket(int sk) {
+    int i = 0;
+
+    if(debug) {
+        i = 1;
+        if(setsockopt(sk, SOL_SOCKET, SO_DEBUG, &i, sizeof(i)) < 0)
+            error("setsockopt SO_DEBUG");
+    }
+
+#ifdef SO_MARK
+    if(fwmark) {
+        if(setsockopt(sk, SOL_SOCKET, SO_MARK,
+                &fwmark, sizeof(fwmark)) < 0
+                )
+            error("setsockopt SO_MARK");
+    }
+#endif
+
+    if(rtbuf && rtbuf_len) {
+        if(af == AF_INET) {
+            if(setsockopt(sk, IPPROTO_IP, IP_OPTIONS,
+                    rtbuf, rtbuf_len) < 0
+                    )
+                error("setsockopt IP_OPTIONS");
+        }
+        else if(af == AF_INET6) {
+            if(setsockopt(sk, IPPROTO_IPV6, IPV6_RTHDR,
+                    rtbuf, rtbuf_len) < 0
+                    )
+                error("setsockopt IPV6_RTHDR");
+        }
+    }
+
+    bind_socket(sk);
+
+    if(af == AF_INET) {
+
+        i = dontfrag ? IP_PMTUDISC_PROBE : IP_PMTUDISC_DONT;
+        if(setsockopt(sk, SOL_IP, IP_MTU_DISCOVER, &i, sizeof(i)) < 0 &&
+                (!dontfrag || (i = IP_PMTUDISC_DO,
+                        setsockopt(sk, SOL_IP, IP_MTU_DISCOVER, &i, sizeof(i)) < 0))
+                )
+            error("setsockopt IP_MTU_DISCOVER");
+
+        if(tos) {
+            i = tos;
+            if(setsockopt(sk, SOL_IP, IP_TOS, &i, sizeof(i)) < 0)
+                error("setsockopt IP_TOS");
+        }
+
+    }
+    else if(af == AF_INET6) {
+
+        i = dontfrag ? IPV6_PMTUDISC_PROBE : IPV6_PMTUDISC_DONT;
+        if(setsockopt(sk, SOL_IPV6, IPV6_MTU_DISCOVER, &i, sizeof(i)) < 0 &&
+                (!dontfrag || (i = IPV6_PMTUDISC_DO,
+                        setsockopt(sk, SOL_IPV6, IPV6_MTU_DISCOVER, &i, sizeof(i)) < 0))
+                )
+            error("setsockopt IPV6_MTU_DISCOVER");
+
+        if(flow_label) {
+            struct in6_flowlabel_req_ flr;
+
+            memset(&flr, 0, sizeof(flr));
+            flr.flr_label = htonl(flow_label & 0x000fffff);
+            flr.flr_action = IPV6_FL_A_GET;
+            flr.flr_flags = IPV6_FL_F_CREATE;
+            flr.flr_share = IPV6_FL_S_ANY;
+            memcpy(&flr.flr_dst, &dst_addr.sin6.sin6_addr,
+                    sizeof(flr.flr_dst));
+
+            if(setsockopt(sk, IPPROTO_IPV6, IPV6_FLOWLABEL_MGR,
+                    &flr, sizeof(flr)) < 0
+                    )
+                error("setsockopt IPV6_FLOWLABEL_MGR");
+        }
+
+        if(tos) {
+            i = tos;
+            if(setsockopt(sk, IPPROTO_IPV6, IPV6_TCLASS,
+                    &i, sizeof(i)) < 0
+                    )
+                error("setsockopt IPV6_TCLASS");
+        }
+
+        if(tos || flow_label) {
+            i = 1;
+            if(setsockopt(sk, IPPROTO_IPV6, IPV6_FLOWINFO_SEND,
+                    &i, sizeof(i)) < 0
+                    )
+                error("setsockopt IPV6_FLOWINFO_SEND");
+        }
+    }
+
+    if(noroute) {
+        i = noroute;
+        if(setsockopt(sk, SOL_SOCKET, SO_DONTROUTE, &i, sizeof(i)) < 0)
+            error("setsockopt SO_DONTROUTE");
+    }
+
+    use_timestamp(sk);
+
+    use_recv_ttl(sk);
+
+    fcntl(sk, F_SETFL, O_NONBLOCK);
+
+    return;
+}
+
+void parse_icmp_res(probe *pb, int type, int code, int info) {
+    char *str = NULL;
+    char buf[sizeof(pb->err_str)];
+
+    if(af == AF_INET) {
+
+        if(type == ICMP_TIME_EXCEEDED) {
+            if(code == ICMP_EXC_TTL)
+                return;
+        }
+        else if(type == ICMP_DEST_UNREACH) {
+
+            switch (code) {
+            case ICMP_UNREACH_NET:
+                case ICMP_UNREACH_NET_UNKNOWN:
+                case ICMP_UNREACH_ISOLATED:
+                case ICMP_UNREACH_TOSNET:
+                str = "!N";
+                break;
+
+            case ICMP_UNREACH_HOST:
+                case ICMP_UNREACH_HOST_UNKNOWN:
+                case ICMP_UNREACH_TOSHOST:
+                str = "!H";
+                break;
+
+            case ICMP_UNREACH_NET_PROHIB:
+                case ICMP_UNREACH_HOST_PROHIB:
+                case ICMP_UNREACH_FILTER_PROHIB:
+                str = "!X";
+                break;
+
+            case ICMP_UNREACH_PORT:
+                /*  dest host is reached   */
+                str = "";
+                break;
+
+            case ICMP_UNREACH_PROTOCOL:
+                str = "!P";
+                break;
+
+            case ICMP_UNREACH_NEEDFRAG:
+                snprintf(buf, sizeof(buf), "!F-%d", info);
+                str = buf;
+                break;
+
+            case ICMP_UNREACH_SRCFAIL:
+                str = "!S";
+                break;
+
+            case ICMP_UNREACH_HOST_PRECEDENCE:
+                str = "!V";
+                break;
+
+            case ICMP_UNREACH_PRECEDENCE_CUTOFF:
+                str = "!C";
+                break;
+
+            default:
+                snprintf(buf, sizeof(buf), "!<%u>", code);
+                str = buf;
+                break;
+            }
+        }
+
+    }
+    else if(af == AF_INET6) {
+
+        if(type == ICMP6_TIME_EXCEEDED) {
+            if(code == ICMP6_TIME_EXCEED_TRANSIT)
+                return;
+        }
+        else if(type == ICMP6_DST_UNREACH) {
+
+            switch (code) {
+
+            case ICMP6_DST_UNREACH_NOROUTE:
+                str = "!N";
+                break;
+
+            case ICMP6_DST_UNREACH_BEYONDSCOPE:
+                case ICMP6_DST_UNREACH_ADDR:
+                str = "!H";
+                break;
+
+            case ICMP6_DST_UNREACH_ADMIN:
+                str = "!X";
+                break;
+
+            case ICMP6_DST_UNREACH_NOPORT:
+                /*  dest host is reached   */
+                str = "";
+                break;
+
+            default:
+                snprintf(buf, sizeof(buf), "!<%u>", code);
+                str = buf;
+                break;
+            }
+        }
+        else if(type == ICMP6_PACKET_TOO_BIG) {
+            snprintf(buf, sizeof(buf), "!F-%d", info);
+            str = buf;
+        }
+    }
+
+    if(!str) {
+        snprintf(buf, sizeof(buf), "!<%u-%u>", type, code);
+        str = buf;
+    }
+
+    if(*str) {
+        strncpy(pb->err_str, str, sizeof(pb->err_str));
+        pb->err_str[sizeof(pb->err_str) - 1] = '\0';
+    }
+    // final present
+    pb->final = 1;
+
+    return;
+}
+
+static void parse_local_res(probe *pb, int ee_errno, int info) {
+
+    if(ee_errno == EMSGSIZE && info != 0) {
+        snprintf(pb->err_str, sizeof(pb->err_str) - 1, "!F-%d", info);
+        pb->final = 1;
+        return;
+    }
+
+    errno = ee_errno;
+    error("local recverr");
+}
+
+void probe_done(probe *pb) {
+
+    if(pb->sk) {
+        del_poll(pb->sk);
+        close(pb->sk);
+        pb->sk = 0;
+    }
+
+    pb->seq = 0;
+
+    pb->done = 1;
+}
+
+void recv_reply(int sk, int err, check_reply_t check_reply) {
+    struct msghdr msg;
+    sockaddr_any from;
+    struct iovec iov;
+    int n;
+    probe *pb;
+    char buf[1280]; /*  min mtu for ipv6 ( >= 576 for ipv4)  */
+    char *bufp = buf;
+    char control[1024];
+    struct cmsghdr *cm;
+    double recv_time = 0;
+    int recv_ttl = 0;
+    struct sock_extended_err *ee = NULL;
+
+    memset(&msg, 0, sizeof(msg));
+    msg.msg_name = &from;
+    msg.msg_namelen = sizeof(from);
+    msg.msg_control = control;
+    msg.msg_controllen = sizeof(control);
+    iov.iov_base = buf;
+    iov.iov_len = sizeof(buf);
+    msg.msg_iov = &iov;
+    msg.msg_iovlen = 1;
+
+    n = recvmsg(sk, &msg, err ? MSG_ERRQUEUE : 0);
+    if(n < 0)
+        return;
+
+    /*  when not MSG_ERRQUEUE, AF_INET returns full ipv4 header
+     on raw sockets...
+     */
+
+    if(!err &&
+            af == AF_INET &&
+            /*  XXX: Assume that the presence of an extra header means
+             that it is not a raw socket...
+             */
+            ops->header_len == 0
+                    ) {
+        struct iphdr *ip = (struct iphdr *) bufp;
+        int hlen;
+
+        if(n < (int) sizeof(struct iphdr))
+            return;
+
+        hlen = ip->ihl << 2;
+        if(n < hlen)
+            return;
+
+        bufp += hlen;
+        n -= hlen;
+    }
+
+    pb = check_reply(sk, err, &from, bufp, n);
+    if(!pb) {
+
+        /*  for `frag needed' case at the local host,
+         kernel >= 3.13 sends local error (no more icmp)
+         */
+        if(!n && err && dontfrag) {
+            pb = &probes[(first_hop - 1) * probes_per_hop];
+            if(pb->done)
+                return;
+        } else
+            return;
+    }
+
+    /*  Parse CMSG stuff   */
+
+    for(cm = CMSG_FIRSTHDR(&msg); cm; cm = CMSG_NXTHDR(&msg, cm)) {
+        void *ptr = CMSG_DATA(cm);
+
+        if(cm->cmsg_level == SOL_SOCKET) {
+
+            if(cm->cmsg_type == SO_TIMESTAMP) {
+                struct timeval *tv = (struct timeval *) ptr;
+
+                recv_time = tv->tv_sec + tv->tv_usec / 1000000.;
+            }
+        }
+        else if(cm->cmsg_level == SOL_IP) {
+
+            if(cm->cmsg_type == IP_TTL)
+                recv_ttl = *((int *) ptr);
+            else if(cm->cmsg_type == IP_RECVERR) {
+
+                ee = (struct sock_extended_err *) ptr;
+
+                if(ee->ee_origin != SO_EE_ORIGIN_ICMP &&
+                        ee->ee_origin != SO_EE_ORIGIN_LOCAL
+                )
+                    return;
+
+                /*  dgram icmp sockets might return extra things...  */
+                if(ee->ee_origin == SO_EE_ORIGIN_ICMP &&
+                        (ee->ee_type == ICMP_SOURCE_QUENCH ||
+                                ee->ee_type == ICMP_REDIRECT)
+                        )
+                    return;
+            }
+        }
+        else if(cm->cmsg_level == SOL_IPV6) {
+
+            if(cm->cmsg_type == IPV6_HOPLIMIT)
+                recv_ttl = *((int *) ptr);
+            else if(cm->cmsg_type == IPV6_RECVERR) {
+
+                ee = (struct sock_extended_err *) ptr;
+
+                if(ee->ee_origin != SO_EE_ORIGIN_ICMP6 &&
+                        ee->ee_origin != SO_EE_ORIGIN_LOCAL
+                )
+                    return;
+            }
+        }
+    }
+
+    if(!recv_time)
+        recv_time = get_time();
+
+    if(!err)
+        memcpy(&pb->res, &from, sizeof(pb->res));
+
+    pb->recv_time = recv_time;
+
+    pb->recv_ttl = recv_ttl;
+
+    if(ee && ee->ee_origin != SO_EE_ORIGIN_LOCAL) { /*  icmp or icmp6   */
+        memcpy(&pb->res, SO_EE_OFFENDER(ee), sizeof(pb->res));
+        parse_icmp_res(pb, ee->ee_type, ee->ee_code, ee->ee_info);
+    }
+
+    if(ee && ee->ee_origin == SO_EE_ORIGIN_LOCAL)
+        parse_local_res(pb, ee->ee_errno, ee->ee_info);
+
+    if(ee &&
+            mtudisc &&
+            ee->ee_info >= header_len &&
+            ee->ee_info < header_len + data_len
+                    ) {
+        data_len = ee->ee_info - header_len;
+
+        probe_done(pb);
+
+        /*  clear this probe (as actually the previous hop answers here)
+         but fill its `err_str' by the info obtained. Ugly, but easy...
+         */
+        memset(pb, 0, sizeof(*pb));
+        snprintf(pb->err_str, sizeof(pb->err_str) - 1, "F=%d", ee->ee_info);
+
+        return;
+    }
+
+    if(ee &&
+            extension &&
+            header_len + n >= (128 + 8) && /*  at least... (rfc4884)  */
+            header_len <= 128 && /*  paranoia   */
+            ((af == AF_INET && (ee->ee_type == ICMP_TIME_EXCEEDED ||
+                    ee->ee_type == ICMP_DEST_UNREACH ||
+                    ee->ee_type == ICMP_PARAMETERPROB)) ||
+                    (af == AF_INET6 && (ee->ee_type == ICMP6_TIME_EXCEEDED ||
+                            ee->ee_type == ICMP6_DST_UNREACH))
+            )
+            ) {
+        int step;
+        int offs = 128 - header_len;
+
+        if(n > (int) data_len)
+            step = 0; /*  guaranteed at 128 ...  */
+        else
+            step = af == AF_INET ? 4 : 8;
+
+        handle_extensions(pb, bufp + offs, n - offs, step);
+    }
+
+    probe_done(pb);
+}
+
+int equal_addr(const sockaddr_any *a, const sockaddr_any *b) {
+
+    if(!a->sa.sa_family)
+        return 0;
+
+    if(a->sa.sa_family != b->sa.sa_family)
+        return 0;
+
+    if(a->sa.sa_family == AF_INET6)
+        return !memcmp(&a->sin6.sin6_addr, &b->sin6.sin6_addr,
+                sizeof(a->sin6.sin6_addr));
+    else
+        return !memcmp(&a->sin.sin_addr, &b->sin.sin_addr,
+                sizeof(a->sin.sin_addr));
+    return 0; /*  not reached   */
+}
+
+void bind_socket(int sk) {
+    sockaddr_any *addr, tmp;
+
+    if(device) {
+        if(setsockopt(sk, SOL_SOCKET, SO_BINDTODEVICE,
+                device, strlen(device) + 1) < 0
+                )
+            error("setsockopt SO_BINDTODEVICE");
+    }
+
+    if(!src_addr.sa.sa_family) {
+        memset(&tmp, 0, sizeof(tmp));
+        tmp.sa.sa_family = af;
+        addr = &tmp;
+    } else
+        addr = &src_addr;
+
+    if(bind(sk, &addr->sa, sizeof(*addr)) < 0)
+        error("bind");
+
+    return;
+}
+
+void use_timestamp(int sk) {
+    int n = 1;
+
+    setsockopt(sk, SOL_SOCKET, SO_TIMESTAMP, &n, sizeof(n));
+    /*  foo on errors...  */
+}
+
+void use_recv_ttl(int sk) {
+    int n = 1;
+
+    if(af == AF_INET)
+        setsockopt(sk, SOL_IP, IP_RECVTTL, &n, sizeof(n));
+    else if(af == AF_INET6)
+        setsockopt(sk, SOL_IPV6, IPV6_RECVHOPLIMIT, &n, sizeof(n));
+    /*  foo on errors   */
+}
+
+void use_recverr(int sk) {
+    int val = 1;
+
+    if(af == AF_INET) {
+        if(setsockopt(sk, SOL_IP, IP_RECVERR, &val, sizeof(val)) < 0)
+            error("setsockopt IP_RECVERR");
+    }
+    else if(af == AF_INET6) {
+        if(setsockopt(sk, SOL_IPV6, IPV6_RECVERR, &val, sizeof(val)) < 0)
+            error("setsockopt IPV6_RECVERR");
+    }
+}
+
+void set_ttl(int sk, int ttl) {
+
+    if(af == AF_INET) {
+        if(setsockopt(sk, SOL_IP, IP_TTL, &ttl, sizeof(ttl)) < 0)
+            error("setsockopt IP_TTL");
+    }
+    else if(af == AF_INET6) {
+        if(setsockopt(sk, SOL_IPV6, IPV6_UNICAST_HOPS,
+                &ttl, sizeof(ttl)) < 0
+                )
+            error("setsockopt IPV6_UNICAST_HOPS");
+    }
+}
+
+int do_send(int sk, const void *data, size_t len, const sockaddr_any *addr) {
+    int res;
+
+    if(!addr || raw_can_connect())
+        res = send(sk, data, len, 0);
+    else
+        res = sendto(sk, data, len, 0, &addr->sa, sizeof(*addr));
+
+    if(res < 0) {
+        if(errno == ENOBUFS || errno == EAGAIN)
+            return res;
+        if(errno == EMSGSIZE)
+            return 0; /*  recverr will say more...  */
+        error("send"); /*  not recoverable   */
+    }
+
+    return res;
+}
+
+/*  There is a bug in the kernel before 2.6.25, which prevents icmp errors
+ to be obtained by MSG_ERRQUEUE for ipv6 connected raw sockets.
+ */
+static int can_connect = -1;
+
+#define VER(A,B,C,D)  (((((((A) << 8) | (B)) << 8) | (C)) << 8) | (D))
+
+int raw_can_connect(void) {
+
+    if(can_connect < 0) {
+
+        if(af == AF_INET)
+            can_connect = 1;
+        else { /*  AF_INET6   */
+            struct utsname uts;
+            int n;
+            unsigned int a, b, c, d = 0;
+
+            if(uname(&uts) < 0)
+                return 0;
+
+            n = sscanf(uts.release, "%u.%u.%u.%u", &a, &b, &c, &d);
+            can_connect = (n >= 3 && VER (a, b, c, d) >= VER(2, 6, 25, 0));
+        }
+    }
+
+    return can_connect;
+}
+
+/**
+ * Traceroute host
+ *
+ * @addr[in] host name or IP address
+ * @hops[out] hops count
+ * @time_usec[out] latency in microseconds
+ * @return 0 Ok, -1 error
+ */
+int traceroute_util(const char *addr, int *hops, int *time_usec)
+{
+    UNUSED(hops);
+    int type = 6; // ipv4 or ipv6
+    int argc = 3;
+    const char *argv[argc];
+    if(type != 4)
+        argv[0] = "traceroute6";
+    else
+        argv[0] = "traceroute4";
+    argv[1] = "-4"; // ipv4
+    argv[2] = addr;
+
+    *hops = 0;
+    *time_usec = 0;
+
+    int ret = traceroute_main(argc, (char**) argv);
+    if(!ret) {
+        ret = -1;
+        for(int i = 0; i < (int) num_probes; i++) {
+            probe *one_probe = probes + i;
+            if(one_probe->done && one_probe->final && one_probe->recv_ttl) {
+                *hops = i / DEF_NUM_PROBES + 1;
+                *time_usec = (int) ((one_probe->recv_time - one_probe->send_time) * 1000000);
+                // if error -> not found host
+                if(!strlen(one_probe->err_str))
+                    ret = 1;
+                break;
+            }
+        }
+        /*if(one_probe->done)
+         printf("%d(%d) dseq=%d sk=%d done=%d final=%d recv_ttl=%d dt=%lf err='%s'\n", i + 1,
+         i / DEF_NUM_PROBES + 1,
+         one_probe->seq, one_probe->sk, one_probe->done,
+         one_probe->final, one_probe->recv_ttl, one_probe->recv_time - one_probe->send_time,
+         one_probe->err_str);*/
+    }
+    free(probes);
+
+    return (ret == 1) ? 0 : ret;
+}
diff --git a/libdap-chain-net/iputils/traceroute/traceroute.h b/libdap-chain-net/iputils/traceroute/traceroute.h
new file mode 100644
index 0000000000000000000000000000000000000000..0db40c544ed51febe2379ba64de5bbfe1b689057
--- /dev/null
+++ b/libdap-chain-net/iputils/traceroute/traceroute.h
@@ -0,0 +1,107 @@
+/*
+    Copyright (c)  2006, 2007		Dmitry Butskoy
+					<buc@citadel.stu.neva.ru>
+    License:  GPL v2 or any later
+
+    See COPYING for the status of this software.
+*/
+
+#include <netinet/in.h>
+
+#include "clif.h"
+#include "../iputils.h"
+
+union common_sockaddr {
+	struct sockaddr sa;
+	struct sockaddr_in sin;
+	struct sockaddr_in6 sin6;
+};
+typedef union common_sockaddr sockaddr_any;
+
+struct probe_struct {
+	int done;
+	int final;
+	sockaddr_any res;
+	double send_time;
+	double recv_time;
+	int recv_ttl;
+	int sk;
+	int seq;
+	char *ext;
+	char err_str[16];	/*  assume enough   */
+};
+typedef struct probe_struct probe;
+
+
+struct tr_module_struct {
+	struct tr_module_struct *next;
+	const char *name;
+	int (*init) (const sockaddr_any *dest,
+				unsigned int port_seq, size_t *packet_len);
+	void (*send_probe) (probe *pb, int ttl);
+	void (*recv_probe) (int fd, int revents);
+	void (*expire_probe) (probe *pb);
+	CLIF_option *options;	/*  per module options, if any   */
+	int one_per_time;	/*  no simultaneous probes   */
+	size_t header_len;	/*  additional header length (aka for udp)   */
+};
+typedef struct tr_module_struct tr_module;
+
+
+#define __TEXT(X)       #X
+#define _TEXT(X)        __TEXT(X)
+
+#define DEF_START_PORT	33434	/*  start for traditional udp method   */
+#define DEF_UDP_PORT	53	/*  dns   */
+#define DEF_TCP_PORT	80	/*  web   */
+#define DEF_DCCP_PORT	DEF_START_PORT	/*  is it a good choice?...  */
+#define DEF_RAW_PROT	253	/*  for experimentation and testing, rfc3692  */
+
+
+void error (const char *str) __attribute__((noreturn));
+void error_or_perm (const char *str) __attribute__((noreturn));
+
+double get_time (void);
+void tune_socket (int sk);
+void parse_icmp_res (probe *pb, int type, int code, int info);
+void probe_done (probe *pb);
+
+typedef probe *(*check_reply_t) (int sk, int err, sockaddr_any *from,
+							char *buf, size_t len);
+void recv_reply (int sk, int err, check_reply_t check_reply);
+
+int equal_addr (const sockaddr_any *a, const sockaddr_any *b);
+
+probe *probe_by_seq (int seq);
+probe *probe_by_sk (int sk);
+
+void bind_socket (int sk);
+void use_timestamp (int sk);
+void use_recv_ttl (int sk);
+void use_recverr (int sk);
+void set_ttl (int sk, int ttl);
+int do_send (int sk, const void *data, size_t len, const sockaddr_any *addr);
+
+void add_poll (int fd, int events);
+void del_poll (int fd);
+void do_poll (double timeout, void (*callback) (int fd, int revents));
+
+void handle_extensions (probe *pb, char *buf, int len, int step);
+const char *get_as_path (const char *query);
+
+int raw_can_connect (void);
+
+unsigned int random_seq (void);
+uint16_t in_csum (const void *ptr, size_t len);
+
+
+void tr_register_module (tr_module *module);
+const tr_module *tr_get_module (const char *name);
+
+#define TR_MODULE(MOD)	\
+static void __init_ ## MOD (void) __attribute__ ((constructor));	\
+static void __init_ ## MOD (void) {	\
+				\
+	tr_register_module (&MOD);	\
+}
+
diff --git a/libdap-chain-net/win32/ip.h b/libdap-chain-net/win32/ip.h
new file mode 100644
index 0000000000000000000000000000000000000000..df3d54a8345b77e0aa00585583c68806b6b8fa0e
--- /dev/null
+++ b/libdap-chain-net/win32/ip.h
@@ -0,0 +1,301 @@
+#pragma once
+
+/* Copyright (C) 1991-2018 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#ifndef __NETINET_IP_H
+#define __NETINET_IP_H 1
+
+//#include <features.h>
+//#include <sys/types.h>
+
+//#include <netinet/in.h>
+
+struct timestamp
+  {
+    uint8_t len;
+    uint8_t ptr;
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+    unsigned int flags:4;
+    unsigned int overflow:4;
+#elif __BYTE_ORDER == __BIG_ENDIAN
+    unsigned int overflow:4;
+    unsigned int flags:4;
+#else
+# error "Please fix <bits/endian.h>"
+#endif
+    uint32_t data[9];
+  };
+
+struct iphdr
+  {
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+    unsigned int ihl:4;
+    unsigned int version:4;
+#elif __BYTE_ORDER == __BIG_ENDIAN
+    unsigned int version:4;
+    unsigned int ihl:4;
+#else
+# error "Please fix <bits/endian.h>"
+#endif
+    uint8_t tos;
+    uint16_t tot_len;
+    uint16_t id;
+    uint16_t frag_off;
+    uint8_t ttl;
+    uint8_t protocol;
+    uint16_t check;
+    uint32_t saddr;
+    uint32_t daddr;
+    /*The options start here. */
+  };
+
+#ifdef __USE_MISC
+/*
+ * Copyright (c) 1982, 1986, 1993
+ *  The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *  @(#)ip.h  8.1 (Berkeley) 6/10/93
+ */
+
+/*
+ * Definitions for internet protocol version 4.
+ * Per RFC 791, September 1981.
+ */
+
+/*
+ * Structure of an internet header, naked of options.
+ */
+struct ip
+  {
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+    unsigned int ip_hl:4;   /* header length */
+    unsigned int ip_v:4;    /* version */
+#endif
+#if __BYTE_ORDER == __BIG_ENDIAN
+    unsigned int ip_v:4;    /* version */
+    unsigned int ip_hl:4;   /* header length */
+#endif
+    uint8_t ip_tos;     /* type of service */
+    unsigned short ip_len;    /* total length */
+    unsigned short ip_id;   /* identification */
+    unsigned short ip_off;    /* fragment offset field */
+#define IP_RF 0x8000      /* reserved fragment flag */
+#define IP_DF 0x4000      /* dont fragment flag */
+#define IP_MF 0x2000      /* more fragments flag */
+#define IP_OFFMASK 0x1fff   /* mask for fragmenting bits */
+    uint8_t ip_ttl;     /* time to live */
+    uint8_t ip_p;     /* protocol */
+    unsigned short ip_sum;    /* checksum */
+    struct in_addr ip_src, ip_dst;  /* source and dest address */
+  };
+
+/*
+ * Time stamp option structure.
+ */
+struct ip_timestamp
+  {
+    uint8_t ipt_code;     /* IPOPT_TS */
+    uint8_t ipt_len;      /* size of structure (variable) */
+    uint8_t ipt_ptr;      /* index of current entry */
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+    unsigned int ipt_flg:4;   /* flags, see below */
+    unsigned int ipt_oflw:4;    /* overflow counter */
+#endif
+#if __BYTE_ORDER == __BIG_ENDIAN
+    unsigned int ipt_oflw:4;    /* overflow counter */
+    unsigned int ipt_flg:4;   /* flags, see below */
+#endif
+    uint32_t data[9];
+  };
+#endif /* __USE_MISC */
+
+#define IPVERSION 4               /* IP version number */
+#define IP_MAXPACKET  65535   /* maximum packet size */
+
+/*
+ * Definitions for Explicit Congestion Notification (ECN)
+ *
+ * Taken from RFC-3168, Section 5.
+ */
+
+#define IPTOS_ECN_MASK    0x03
+#define IPTOS_ECN(x)    ((x) & IPTOS_ECN_MASK)
+#define IPTOS_ECN_NOT_ECT 0x00
+#define IPTOS_ECN_ECT1    0x01
+#define IPTOS_ECN_ECT0    0x02
+#define IPTOS_ECN_CE    0x03
+
+/*
+ * Definitions for IP differentiated services code points (DSCP)
+ *
+ * Taken from RFC-2597, Section 6 and RFC-2598, Section 2.3.
+ */
+
+#define IPTOS_DSCP_MASK   0xfc
+#define IPTOS_DSCP(x)   ((x) & IPTOS_DSCP_MASK)
+#define IPTOS_DSCP_AF11   0x28
+#define IPTOS_DSCP_AF12   0x30
+#define IPTOS_DSCP_AF13   0x38
+#define IPTOS_DSCP_AF21   0x48
+#define IPTOS_DSCP_AF22   0x50
+#define IPTOS_DSCP_AF23   0x58
+#define IPTOS_DSCP_AF31   0x68
+#define IPTOS_DSCP_AF32   0x70
+#define IPTOS_DSCP_AF33   0x78
+#define IPTOS_DSCP_AF41   0x88
+#define IPTOS_DSCP_AF42   0x90
+#define IPTOS_DSCP_AF43   0x98
+#define IPTOS_DSCP_EF   0xb8
+
+/*
+ * In RFC 2474, Section 4.2.2.1, the Class Selector Codepoints subsume
+ * the old ToS Precedence values.
+ */
+
+#define IPTOS_CLASS_MASK    0xe0
+#define IPTOS_CLASS(class)    ((class) & IPTOS_CLASS_MASK)
+#define IPTOS_CLASS_CS0     0x00
+#define IPTOS_CLASS_CS1     0x20
+#define IPTOS_CLASS_CS2     0x40
+#define IPTOS_CLASS_CS3     0x60
+#define IPTOS_CLASS_CS4     0x80
+#define IPTOS_CLASS_CS5     0xa0
+#define IPTOS_CLASS_CS6     0xc0
+#define IPTOS_CLASS_CS7     0xe0
+
+#define IPTOS_CLASS_DEFAULT   IPTOS_CLASS_CS0
+
+/*
+ * Definitions for IP type of service (ip_tos) [deprecated; use DSCP
+ * and CS definitions above instead.]
+ */
+#define IPTOS_TOS_MASK    0x1E
+#define IPTOS_TOS(tos)    ((tos) & IPTOS_TOS_MASK)
+#define IPTOS_LOWDELAY    0x10
+#define IPTOS_THROUGHPUT  0x08
+#define IPTOS_RELIABILITY 0x04
+#define IPTOS_LOWCOST   0x02
+#define IPTOS_MINCOST   IPTOS_LOWCOST
+
+/*
+ * Definitions for IP precedence (also in ip_tos) [also deprecated.]
+ */
+#define IPTOS_PREC_MASK     IPTOS_CLASS_MASK
+#define IPTOS_PREC(tos)     IPTOS_CLASS(tos)
+#define IPTOS_PREC_NETCONTROL   IPTOS_CLASS_CS7
+#define IPTOS_PREC_INTERNETCONTROL  IPTOS_CLASS_CS6
+#define IPTOS_PREC_CRITIC_ECP   IPTOS_CLASS_CS5
+#define IPTOS_PREC_FLASHOVERRIDE  IPTOS_CLASS_CS4
+#define IPTOS_PREC_FLASH    IPTOS_CLASS_CS3
+#define IPTOS_PREC_IMMEDIATE    IPTOS_CLASS_CS2
+#define IPTOS_PREC_PRIORITY   IPTOS_CLASS_CS1
+#define IPTOS_PREC_ROUTINE    IPTOS_CLASS_CS0
+
+/*
+ * Definitions for options.
+ */
+#define IPOPT_COPY    0x80
+#define IPOPT_CLASS_MASK  0x60
+#define IPOPT_NUMBER_MASK 0x1f
+
+#define IPOPT_COPIED(o)   ((o) & IPOPT_COPY)
+#define IPOPT_CLASS(o)    ((o) & IPOPT_CLASS_MASK)
+#define IPOPT_NUMBER(o)   ((o) & IPOPT_NUMBER_MASK)
+
+#define IPOPT_CONTROL   0x00
+#define IPOPT_RESERVED1   0x20
+#define IPOPT_DEBMEAS   0x40
+#define IPOPT_MEASUREMENT       IPOPT_DEBMEAS
+#define IPOPT_RESERVED2   0x60
+
+#define IPOPT_EOL   0   /* end of option list */
+#define IPOPT_END   IPOPT_EOL
+#define IPOPT_NOP   1   /* no operation */
+#define IPOPT_NOOP    IPOPT_NOP
+
+#define IPOPT_RR    7   /* record packet route */
+#define IPOPT_TS    68    /* timestamp */
+#define IPOPT_TIMESTAMP   IPOPT_TS
+#define IPOPT_SECURITY    130   /* provide s,c,h,tcc */
+#define IPOPT_SEC   IPOPT_SECURITY
+#define IPOPT_LSRR    131   /* loose source route */
+#define IPOPT_SATID   136   /* satnet id */
+#define IPOPT_SID   IPOPT_SATID
+#define IPOPT_SSRR    137   /* strict source route */
+#define IPOPT_RA    148   /* router alert */
+
+/*
+ * Offsets to fields in options other than EOL and NOP.
+ */
+#define IPOPT_OPTVAL    0   /* option ID */
+#define IPOPT_OLEN    1   /* option length */
+#define IPOPT_OFFSET    2   /* offset within option */
+#define IPOPT_MINOFF    4   /* min value of above */
+
+#define MAX_IPOPTLEN    40
+
+/* flag bits for ipt_flg */
+#define IPOPT_TS_TSONLY   0   /* timestamps only */
+#define IPOPT_TS_TSANDADDR  1   /* timestamps and addresses */
+#define IPOPT_TS_PRESPEC  3   /* specified modules only */
+
+/* bits for security (not byte swapped) */
+#define IPOPT_SECUR_UNCLASS 0x0000
+#define IPOPT_SECUR_CONFID  0xf135
+#define IPOPT_SECUR_EFTO  0x789a
+#define IPOPT_SECUR_MMMM  0xbc4d
+#define IPOPT_SECUR_RESTR 0xaf13
+#define IPOPT_SECUR_SECRET  0xd788
+#define IPOPT_SECUR_TOPSECRET 0x6bc5
+
+/*
+ * Internet implementation parameters.
+ */
+#define MAXTTL    255   /* maximum time to live (seconds) */
+#define IPDEFTTL  64    /* default ttl, from RFC 1340 */
+#define IPFRAGTTL 60    /* time to live for frags, slowhz */
+#define IPTTLDEC  1   /* subtracted when forwarding */
+
+#define IP_MSS    576   /* default maximum segment size */
+
+
+#endif /* netinet/ip.h */
diff --git a/libdap-chain-net/win32/iphdr.h b/libdap-chain-net/win32/iphdr.h
new file mode 100644
index 0000000000000000000000000000000000000000..f2533f910cce0e5c6f35a56018a4bea307313b27
--- /dev/null
+++ b/libdap-chain-net/win32/iphdr.h
@@ -0,0 +1,112 @@
+#pragma once
+
+// Align on a 1-byte boundary
+//#include <pshpack1.h>
+
+#pragma pack(push,1)
+
+// IPv4 header
+typedef struct ip_hdr
+{
+  unsigned char  ip_verlen;        // 4-bit IPv4 version
+                                     // 4-bit header length (in 32-bit words)
+  unsigned char  ip_tos;           // IP type of service
+  unsigned short ip_totallength;   // Total length
+  unsigned short ip_id;            // Unique identifier 
+  unsigned short ip_offset;        // Fragment offset field
+  unsigned char  ip_ttl;           // Time to live
+  unsigned char  ip_protocol;      // Protocol(TCP,UDP etc)
+  unsigned short ip_checksum;      // IP checksum
+  unsigned int   ip_srcaddr;       // Source address
+  unsigned int   ip_destaddr;      // Source address
+
+} IPV4_HDR, *PIPV4_HDR, FAR * LPIPV4_HDR;
+
+// IPv4 option header
+typedef struct ipv4_option_hdr
+{
+  unsigned char   opt_code;           // option type
+  unsigned char   opt_len;            // length of the option header
+  unsigned char   opt_ptr;            // offset into options
+  unsigned long   opt_addr[9];        // list of IPv4 addresses
+} IPV4_OPTION_HDR, *PIPV4_OPTION_HDR, FAR *LPIPV4_OPTION_HDR;
+
+// ICMP header
+typedef struct icmp_hdr
+{
+  unsigned char   icmp_type;
+  unsigned char   icmp_code;
+  unsigned short  icmp_checksum;
+  unsigned short  icmp_id;
+  unsigned short  icmp_sequence;
+} ICMP_HDR, *PICMP_HDR, FAR *LPICMP_HDR;
+
+// IPv6 protocol header
+typedef struct ipv6_hdr
+{
+  unsigned long   ipv6_vertcflow;        // 4-bit IPv6 version
+                                           // 8-bit traffic class
+                                           // 20-bit flow label
+  unsigned short  ipv6_payloadlen;       // payload length
+  unsigned char   ipv6_nexthdr;          // next header protocol value
+  unsigned char   ipv6_hoplimit;         // TTL 
+  struct in6_addr ipv6_srcaddr;          // Source address
+  struct in6_addr ipv6_destaddr;         // Destination address
+} IPV6_HDR, *PIPV6_HDR, FAR * LPIPV6_HDR;
+
+// IPv6 fragment header
+typedef struct ipv6_fragment_hdr
+{
+  unsigned char   ipv6_frag_nexthdr;
+  unsigned char   ipv6_frag_reserved;
+  unsigned short  ipv6_frag_offset;
+  unsigned long   ipv6_frag_id;
+} IPV6_FRAGMENT_HDR, *PIPV6_FRAGMENT_HDR, FAR * LPIPV6_FRAGMENT_HDR;
+
+// ICMPv6 header
+typedef struct icmpv6_hdr {
+  unsigned char   icmp6_type;
+  unsigned char   icmp6_code;
+  unsigned short  icmp6_checksum;
+} ICMPV6_HDR;
+
+// ICMPv6 echo request body
+typedef struct icmpv6_echo_request
+{
+  unsigned short  icmp6_echo_id;
+  unsigned short  icmp6_echo_sequence;
+} ICMPV6_ECHO_REQUEST;
+
+// Define the UDP header 
+typedef struct udp_hdr
+{
+  unsigned short src_portno;       // Source port no.
+  unsigned short dst_portno;       // Dest. port no.
+  unsigned short udp_length;       // Udp packet length
+  unsigned short udp_checksum;     // Udp checksum (optional)
+} UDP_HDR, *PUDP_HDR;
+
+// IPv4 option for record route
+#define IP_RECORD_ROUTE     0x7
+
+// ICMP6 protocol value (used in the socket call and IPv6 header)
+#define IPPROTO_ICMP6       58
+
+// ICMP types and codes
+#define ICMPV4_ECHO_REQUEST_TYPE   8
+#define ICMPV4_ECHO_REQUEST_CODE   0
+#define ICMPV4_ECHO_REPLY_TYPE     0
+#define ICMPV4_ECHO_REPLY_CODE     0
+#define ICMPV4_MINIMUM_HEADER      8
+
+// ICPM6 types and codes
+#define ICMPV6_ECHO_REQUEST_TYPE   128
+#define ICMPV6_ECHO_REQUEST_CODE   0
+#define ICMPV6_ECHO_REPLY_TYPE     129
+#define ICMPV6_ECHO_REPLY_CODE     0
+
+#pragma pack(pop)
+
+// Restore byte alignment back to default
+
+//#include <poppack.h>