summaryrefslogtreecommitdiffstats
path: root/tcllib/modules/dns
diff options
context:
space:
mode:
Diffstat (limited to 'tcllib/modules/dns')
-rw-r--r--tcllib/modules/dns/ChangeLog385
-rw-r--r--tcllib/modules/dns/dns-url.txt728
-rw-r--r--tcllib/modules/dns/dns.tcl1416
-rw-r--r--tcllib/modules/dns/dns.test73
-rw-r--r--tcllib/modules/dns/ip.tcl553
-rw-r--r--tcllib/modules/dns/ip.test271
-rw-r--r--tcllib/modules/dns/ipMore.tcl1295
-rw-r--r--tcllib/modules/dns/ipMore.test803
-rw-r--r--tcllib/modules/dns/ipMoreC.tcl242
-rw-r--r--tcllib/modules/dns/msgs/en.msg8
-rw-r--r--tcllib/modules/dns/pkgIndex.tcl9
-rw-r--r--tcllib/modules/dns/resolv.tcl249
-rw-r--r--tcllib/modules/dns/spf.tcl528
-rw-r--r--tcllib/modules/dns/spf.test244
-rw-r--r--tcllib/modules/dns/tcllib_dns.man242
-rw-r--r--tcllib/modules/dns/tcllib_ip.man451
16 files changed, 7497 insertions, 0 deletions
diff --git a/tcllib/modules/dns/ChangeLog b/tcllib/modules/dns/ChangeLog
new file mode 100644
index 0000000..01f57c0
--- /dev/null
+++ b/tcllib/modules/dns/ChangeLog
@@ -0,0 +1,385 @@
+2013-07-26 Andreas Kupries <andreask@activestate.com>
+
+ * ip.tcl: [AS Bug 99728]: Fixed version mismatch code vs package
+ index. Bug introduced by last change, below.
+
+2013-03-25 Andreas Kupries <andreask@activestate.com>
+
+ * ip.tcl: [Bug 3608943] Check ip::version for empty input and
+ * ip.test: react properly. Extended testsuite. Bumped version
+ * pkgIndex.tcl: to 1.2.1.
+ * tcllib_ip.man:
+
+2013-02-01 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.15 ========================
+ *
+
+2011-12-13 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.14 ========================
+ *
+
+2011-01-24 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.13 ========================
+ *
+
+2010-08-16 Andreas Kupries <andreask@activestate.com>
+
+ * ip.tcl: Added new commands 'collapse' and 'subtract' for
+ * ip.test: more 'arithmetic' on network ranges. Extended
+ * pkgIndex.tcl: documentation and testsuite. Version bumped
+ * tcllib_ip.man: to 1.2. Base code by Roy Keene, with thanks.
+
+2009-12-07 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.12 ========================
+ *
+
+2009-04-13 Andreas Kupries <andreask@activestate.com>
+
+ * ip.tcl (::ip::IPv4?, ::ip::version): Corrected check for colons
+ * tcllib_ip.man: (wrong order of arguments), and moved this check,
+ * pkgIndex.tcl: a speed optimization from the look of it, into the
+ main IPv4 test to be used everywhere. Bumped the package version
+ to 1.1.3. Fixes [Bug 2123397].
+
+2008-12-12 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.11.1 ========================
+ *
+
+2008-11-22 Michael Schlenker <mic42@users.sourceforge.net>
+
+ * dns.tcl: Fixed typo in flags for errorcode decoding
+
+2008-10-16 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.11 ========================
+ *
+
+2008-03-14 Andreas Kupries <andreask@activestate.com>
+
+ * spf.tcl (::spf::_exists): Fixed bad use of 'return', reported in
+ * pkgIndex.tcl: [SF Tcllib Bug 1826418], by Erik Leunissen. Bumped
+ to version 1.1.1.
+
+2007-09-12 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.10 ========================
+ *
+
+2007-08-26 Pat Thoyts <patthoyts@users.sourceforge.net>
+
+ * dns.tcl: Applied patch #1610330 from Sergei Golovan to provide
+ asynchronous connection for dns over tcp.
+
+2007-08-25 Pat Thoyts <patthoyts@users.sourceforge.net>
+
+ * tcllib_dns.man: Documented the -loglevel configure option.
+
+2007-08-22 Andreas Kupries <andreask@activestate.com>
+
+ * spf.test: Added proper requisites to the testsuite.
+
+2007-08-20 Andreas Kupries <andreask@activestate.com>
+
+ * tcllib_ip.man: Bumped version to 1.1.2 due to the bugfix made
+ * ip.tcl: by the last change.
+ * pkgIndex.tcl:
+
+2007-07-05 Pat Thoyts <patthoyts@users.sourceforge.net>
+
+ * ip.tcl: bug #1739359 - reject domain names that look like
+ * ip.test: ipv4 addresses
+
+2007-03-21 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * tcllib_ip.man: Fixed all warnings due to use of now deprecated
+ * tcllib_dns.man: commands. Added a section about how to give feedback.
+
+2006-11-04 Pat Thoyts <patthoyts@users.sourceforge.net>
+
+ * ipMoreC.tcl: Silence critcl warning.
+
+2006-10-03 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.9 ========================
+ *
+
+2006-09-19 Andreas Kupries <andreask@activestate.com>
+
+ * tcllib_ip.man: Bumped version to 1.1.1
+ * ip.tcl:
+ * pkgIndex.tcl:
+
+2006-05-05 Pat Thoyts <patthoyts@users.sourceforge.net>
+
+ * dns.tcl: Extended the nameservers command to work on Win9x
+ systems and we now make use of this to initially configure a
+ default nameserver. Some minor additional cleanup.
+
+2006-04-21 Pat Thoyts <patthoyts@users.sourceforge.net>
+
+ * dns.tcl: Fixed bug #1158037. We were using the query id to
+ locate the DNS state token but this restricts us to 65535 queries
+ as the value is packed into a short.
+
+2006-04-20 Pat Thoyts <patthoyts@users.sourceforge.net>
+
+ * dns.tcl: Applied patch from #1453327 by Segei Golovan to improve
+ support for TXT records.
+
+2006-01-29 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * spf.test: Fixed use of duplicate test names.
+
+2006-01-22 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * dns.test: More boilerplate simplified via use of test support.
+ * ip.test:
+ * ipMore.test:
+ * spf.test:
+
+2006-01-19 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * dns.test: Hooked into the new common test support code.
+ * ip.test:
+ * ipMore.test:
+ * spf.test
+
+2005-10-21 Andreas Kupries <andreask@activestate.com>
+
+ * ipMore.tcl: Style cleanup. We need only one $Id expansion at
+ the top of the file, not for every command in it.
+
+2005-10-18 Andreas Kupries <andreask@activestate.com>
+
+ * ipMore.tcl (::ip::maskToInt): Fixed [SF Tcllib Bug 1323146],
+ using the patch supplied by Mikhail Teterin
+ <kot@users.sf.net>. One path through the code did not mask the
+ data down to 32bit.
+
+2005-10-06 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.8 ========================
+ *
+
+2005-10-04 Andreas Kupries <andreask@activestate.com>
+
+ * ipMoreC.tcl: disabling the new critcl parts of ip for the
+ platforms it is known to not work for. A better solution will be
+ worked on after the release.
+
+2005-09-30 Andreas Kupries <andreask@activestate.com>
+
+ * ipMore.test: Integrated Aamer Akhter's extended
+ * ipMore.tcl: conversion and manipulation commands
+ * ipMoreC.tcl: for ip-addresses and -masks. See the
+ * msmgs/en.msg: [SF Tcllib Patch 1260196]. Extended the
+ * ip.man: documentation, testsuite, critcl setup.
+
+2005-05-21 Pat Thoyts <pat@zsplat.freeserve.co.uk>
+
+ * dns.tcl: Added support for ceptcl as well as tcludp for udp
+ support.
+
+2005-05-06 Pat Thoyts <patthoyts@users.sourceforge.net>
+
+ * ip.tcl: Added some support for acceping RFC3056 6to4 addresses
+ * ip.test: of the form 2002:<ipv4 address>::/48
+
+2005-04-07 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * dns.tcl (TcpEvent): Fixed [SF Tcllib Bug 1173444]. The cause was
+ a series of typos, the procedure argument 'token' was referenced
+ to in the code via 'tok'.
+
+2004-11-21 Pat Thoyts <patthoyts@users.sourceforge.net>
+
+ * dns.tcl: Fixed bug in ReadUShort.
+
+ * dns.tcl: Incremented the version to 1.2.1 and updated the
+ * ip.tcl: manual.
+
+ * dns.tcl: Added support for RFC2782 (DNS SRV) which provides
+ for service discovery via DNS.
+ Added dns::nameservers command to return the list
+ of nameservers configured -- this is not
+ necessarily all that reliable but should be useful.
+ Implemented for Windows and Unix.
+
+ * ip.tcl: Added an error message to deal with invalid address
+ formats during normalization.
+
+2004-11-06 Pat Thoyts <patthoyts@users.sourceforge.net>
+
+ * ip.tcl: Bug #1060460 - support for IPv4 in IPv6-style
+ addresses in ip::normalize.
+
+2004-10-19 Pat Thoyts <patthoyts@users.sourceforge.net>
+
+ * dns.tcl: Applied fix for bug #1018934 "incorrectly
+ detecting query as a reverse lookup"
+
+2004-10-05 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.7 ========================
+ *
+
+2004-07-31 Pat Thoyts <patthoyts@users.sourceforge.net>
+
+ * spf.test: Added lots of macro tests and fixed some bugs
+ * spf.tcl: that this revealed.
+
+2004-07-30 Pat Thoyts <patthoyts@users.sourceforge.net>
+
+ * spf.tcl: Updated to draft-ietf-marid-protocol-00 document.
+ * spf.test: Fully implements section 7 macro expansion.
+
+2004-07-23 Pat Thoyts <patthoyts@users.sourceforge.net>
+
+ * spf.tcl: Use ip package and implemented IPv6 type.
+
+ * dns.tcl: Added support for IPv6 lookups (type AAAA).
+
+ * ip.tcl: NEW: IP address package
+ * ip.test: tests (ipv4 and ipv6)
+ * tcllib_ip.man: Manual page
+
+2004-06-01 Pat Thoyts <patthoyts@users.sourceforge.net>
+
+ * spf.tcl: NEW: Implementation of SPF using our dns package.
+ * spf.test: NEW: tests for SPF package.
+ * pkgIndex.tcl: Updated to include SPF.
+
+ * dns-url.txt: Updated the dns-url document to -09 version. This
+ implementation is still valid so no changes to the uri code.
+
+2004-05-26 Pat Thoyts <patthoyts@users.sourceforge.net>
+
+ * dns.tcl: Fix issue setting the log level properly.
+
+2004-05-23 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.6.1 ========================
+ *
+
+2004-02-15 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.6 ========================
+ *
+
+2004-01-22 Pat Thoyts <patthoyts@users.sourceforge.net>
+
+ * dns.tcl: Added automatic recognition of reverse lookups (where
+ query is 1.2.3.4). These are converted to in-addr.arpa lookups.
+ Added a dns::result to return the whole decoded answer record.
+ Added SPF record type (an alias for TXT).
+ Incremented package version to 1.0.5
+
+2003-07-09 Pat Thoyts <patthoyts@users.sourceforge.net>
+
+ * dns.tcl: Added decoding for SOA response records.
+
+2003-05-09 Andreas Kupries <andreask@activestate.com>
+
+ * resolv.tcl (::resolv::init): Added missing [expr] bracing.
+
+2003-05-05 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.4 ========================
+ *
+
+2003-04-14 Pat Thoyts <patthoyts@users.sourceforge.net>
+
+ * dns.tcl: Added error message to the timeout.
+ * resolv.tcl: incorporated some of Emmanuel's updated code.
+
+2003-04-12 Pat Thoyts <patthoyts@users.sourceforge.net>
+
+ * dns.man: *Renamed* to tcllib_dns.man to avoid a name clash with
+ the dns manpage from the scotty package.
+
+2003-04-11 Pat Thoyts <patthoyts@users.sourceforge.net>
+
+ * dns.tcl: Try to read the whole reply when using tcp. Added a
+ catch to avoid bgerrors within the handler.
+ * dns.tcl:
+ * dns.man:
+ * pkgIndex.tcl: hiked version to 1.0.4
+
+2003-04-11 Andreas Kupries <andreask@pliers.activestate.com>
+
+ * dns.tcl:
+ * dns.man:
+ * pkgIndex.tcl: Fixed bug #614591. Set version of the package to
+ to 1.0.3 throughout. Added package 'resolv' to index.
+
+2003-03-04 Pat Thoyts <patthoyts@users.sourceforge.net>
+
+ * dns.tcl: converted from the log package to logger. Enable UDP as
+ the default if available.
+
+2003-02-27 Pat Thoyts <patthoyts@users.sourceforge.net>
+
+ * resolv.tcl: Imported Emmanuel Frecon's code from the Tclers
+ Wiki. Provides a name cache and simplifies usage of the dns
+ package.
+
+2003-02-25 Pat Thoyts <patthoyts@users.sourceforge.net>
+
+ * dns.tcl: Tested the UDP transmission using a fixed TclUDP.
+ * dns.tcl: Implemented inverse queries. (Pretty useless though).
+ * dns.tcl: Added errorcode procedure.
+
+2003-01-30 Pat Thoyts <patthoyts@users.sourceforge.net>
+
+ * dns.tcl: Implemented UDP transmission. Currently not tested
+ because tcludp doesn't handle binary data.
+
+2003-01-24 Pat Thoyts <patthoyts@users.sourceforge.net>
+
+ * pkgIndex.tcl:
+ * dns.man: Added Tcl 8.2 as minimum Tcl version to resolve bug
+ * dns.tcl: #674330. Upped version to 1.0.2
+ * dns.test: Added some tests for the dns uri handling and fixed a
+ bug in decoding the class and type section.
+
+2003-01-16 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * dns.man: More semantic markup, less visual one.
+
+2002-08-30 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * dns.tcl: Updated 'info exist' to 'info exists'.
+
+2002-06-07 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * dns.man:
+ * dns.tcl:
+ * pkgIndex.tcl: Version up to 1.0.1
+
+ * dns.tcl: moved var initialization code to the end, as it uses
+ the 'dns::configure' command, and thus should be called after
+ its definition. This is the reason for bug #564670, thus now
+ fixed.
+
+2002-06-05 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * dns.man: Added note to manpage regarding DNS via TCP and
+ possible pitfalls.
diff --git a/tcllib/modules/dns/dns-url.txt b/tcllib/modules/dns/dns-url.txt
new file mode 100644
index 0000000..bed51f5
--- /dev/null
+++ b/tcllib/modules/dns/dns-url.txt
@@ -0,0 +1,728 @@
+
+
+Network Working Group S. Josefsson
+Internet-Draft October 26, 2003
+Expires: April 25, 2004
+
+
+ Domain Name System Uniform Resource Identifiers
+ draft-josefsson-dns-url-09
+
+Status of this Memo
+
+ This document is an Internet-Draft and is in full conformance with
+ all provisions of Section 10 of RFC2026.
+
+ Internet-Drafts are working documents of the Internet Engineering
+ Task Force (IETF), its areas, and its working groups. Note that other
+ groups may also distribute working documents as Internet-Drafts.
+
+ Internet-Drafts are draft documents valid for a maximum of six months
+ and may be updated, replaced, or obsoleted by other documents at any
+ time. It is inappropriate to use Internet-Drafts as reference
+ material or to cite them other than as "work in progress."
+
+ The list of current Internet-Drafts can be accessed at http://
+ www.ietf.org/ietf/1id-abstracts.txt.
+
+ The list of Internet-Draft Shadow Directories can be accessed at
+ http://www.ietf.org/shadow.html.
+
+ This Internet-Draft will expire on April 25, 2004.
+
+Copyright Notice
+
+ Copyright (C) The Internet Society (2003). All Rights Reserved.
+
+Abstract
+
+ This document define Uniform Resource Identifiers for Domain Name
+ System resources.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Josefsson Expires April 25, 2004 [Page 1]
+
+Internet-Draft DNS URI October 2003
+
+
+Table of Contents
+
+ 1. Introduction and Background . . . . . . . . . . . . . . . . . 3
+ 2. DNS URI Registration . . . . . . . . . . . . . . . . . . . . . 4
+ 3. Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
+ 4. Security Considerations . . . . . . . . . . . . . . . . . . . 8
+ 5. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 8
+ Normative References . . . . . . . . . . . . . . . . . . . . . 9
+ Informative References . . . . . . . . . . . . . . . . . . . . 9
+ Author's Address . . . . . . . . . . . . . . . . . . . . . . . 10
+ A. Revision Changes . . . . . . . . . . . . . . . . . . . . . . . 10
+ A.1 Changes since -06 . . . . . . . . . . . . . . . . . . . . . . 10
+ A.2 Changes since -07 . . . . . . . . . . . . . . . . . . . . . . 10
+ A.3 Changes since -08 . . . . . . . . . . . . . . . . . . . . . . 10
+ Intellectual Property and Copyright Statements . . . . . . . . 12
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Josefsson Expires April 25, 2004 [Page 2]
+
+Internet-Draft DNS URI October 2003
+
+
+1. Introduction and Background
+
+ The Domain Name System (DNS) [1][2] is a widely deployed system used
+ to, among other things, translate host names into IP addresses.
+ Recent work has added support for storing certificates and
+ certificate revocation lists in the DNS [10].
+
+ The primary motivation behind defining a Uniform Resource Identifier
+ (URI) for DNS resources, instead of using another non-URI syntax that
+ embed the domain, type value and class value, is that applications
+ that stores or retrieve certificates today uses URIs for this
+ purpose. Thus, defining a URI scheme for DNS resources allows these
+ existing protocols to be used with certificates in the DNS without
+ having to add DNS specific modifications to said protocols. In order
+ to not introduce interoperability or security considerations,
+ protocols that uses these URIs naturally must have been written to
+ allow for future, as of writing yet undefined, URIs to be used.
+
+ A few examples of protocols that may utilize DNS URIs:
+
+ o The OpenPGP Message Format [8], where an end-user may indicate the
+ location of a copy of any updates to her key, using the "preferred
+ key server" field.
+
+ o The X.509 Online Certificate Status Protocol [11], where the OCSP
+ responder can indicate where a CRL is found, using the
+ id-pkix-ocsp-crl extension.
+
+ The DNS URI scheme defined here can, of course, be used to reference
+ any DNS data, and is not limited to only certificates. The purpose
+ of this specification is to define a generic DNS URI, not a specific
+ DNS solution for certificates stored in the DNS. Browsers may
+ implement support for DNS URIs by forming DNS queries and render DNS
+ responses using HTML [14], similar to what is done for the FTP [5].
+
+ The core part of this document is the URI Registration Template
+ according to [13].
+
+ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
+ "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
+ document are to be interpreted as described in RFC 2119 [6].
+
+
+
+
+
+
+
+
+
+
+Josefsson Expires April 25, 2004 [Page 3]
+
+Internet-Draft DNS URI October 2003
+
+
+2. DNS URI Registration
+
+ URL scheme name: "dns".
+
+ URL scheme syntax: A DNS URI designates a DNS resource record set
+ that can be referenced by domain name, type, class and optionally the
+ authority. The DNS URI follows the generic syntax from RFC 2396 [4],
+ and is described using ABNF [3]. Strings are not case sensitive and
+ free insertion of linear-white-space is not permitted.
+
+ dnsurl = "dns:" [ "//" dnsauthority "/" ] dnsname ["?" dnsquery]
+
+ dnsauthority = hostport
+ ; See RFC 2396 for "hostport" definition.
+
+ dnsname = *pchar
+ ; See RFC 2396 for "pchar" definition.
+ ; NB! Can be empty.
+
+ dnsquery = dnsqueryelement [";" dnsquery]
+ ; First matching element MUST be used.
+ ; E.g., dns:host.example.org?TYPE=A;TYPE=TXT
+ ; means type A.
+
+ dnsqueryelement = ( "CLASS=" dnsclassval ) / ( "TYPE=" dnstypeval ) /
+ ( 1*alphanum "=" 1*alphanum )
+
+ dnsclassval = 1*digit / "IN" / "CH" / ...
+ ; Any IANA registered DNS class expressed as
+ ; mnemonic or as decimal integer.
+
+ dnstypeval = 1*digit / "A" / "NS" / "MD" / ...
+ ; Any IANA registered DNS type expressed as
+ ; mnemonic or as decimal integer.
+
+ The digit representation of types and classes MAY be used when a
+ mnemonic for the corresponding value is not well known (e.g., for
+ newly introduced types or classes), but SHOULD NOT be used for the
+ types or classes defined in the DNS specification [2]. All
+ implementations MUST recognize the mnemonics defined in [2].
+
+ Unless specified in the URI, the authority ("dnsauthority") is
+ assumed to be locally known, "dnsclassval" to be the Internet class
+ ("IN"), and "dnstypeval" to be the Address type ("A").
+
+ To resolve a DNS URI using the DNS protocol [2] a query is formed by
+ using the dnsname, dnsclassval and dnstypeval from the URI string (or
+ the previously mentioned default values if some value missing from
+
+
+
+Josefsson Expires April 25, 2004 [Page 4]
+
+Internet-Draft DNS URI October 2003
+
+
+ the string). If authority ("dnsauthority") is given in the URI
+ string, this indicate the server that should receive the DNS query,
+ otherwise the default DNS server should receive it. (Note that DNS
+ URIs could be resolved by other protocols than the DNS protocol. DNS
+ URIs does not require the use of the DNS protocol, although it is
+ expected to be the typical usage. This paragraph only illustrate how
+ DNS URIs are resolved using the DNS protocol.)
+
+ A client MAY want to check that it understands the dnsclassval and
+ dnstypeval before sending a query, so that it is able to correctly
+ parse the answer. A typical example of a client that would not need
+ to check dnsclassval and dnstypeval would be a proxy that just treat
+ the answer as opaque data.
+
+ Character encoding considerations: The characters are encoded as per
+ the "URI Generic Syntax" RFC [4]. The DNS protocol do not consider
+ character sets, it simply transports opaque data. In particular, the
+ "dnsname" field of the DNS URI is to be considered an
+ internationalized domain name (IDN) unaware domain name slot, in the
+ terminology of [16]. (The reason for this is that making these fields
+ be IDN aware by, e.g., specifying that they are UTF-8 [7] strings,
+ would require further encoding mechanisms to be able to express all
+ valid DNS domain names. This is because the DNS allows all octet
+ sequences to be used as domain labels, so UTF-8 strings do not cover
+ all possibilities. Instead of defining further encoding mechanisms,
+ we point applications with internationalization needs at the ASCII
+ encoding described in [16] which should be satisfactory.) The
+ considerations for "hostport" are discussed in [4]
+
+ To encode a "." that is part of a DNS label the "escaped" encoding
+ MUST be used, and a label delimiter MUST be encoded as ".". That is,
+ the only way to encode a label delimiter is ".", and the only way to
+ encode a "." as part of label is "%2e". This approach was chosen to
+ minimize the modifications users will have to do when manually
+ translating a domain name string into the URI form.
+
+ This URI specification allows all possible domain names to be encoded
+ (of course following the encoding rules of [4]), however certain
+ applications may restrict the set of valid characters and care should
+ be taken so that invalid characters in these contexts does not cause
+ harm. In particular, host names in the DNS have certain
+ restrictions. It is up to these application to limit this subset,
+ this URI scheme places no restrictions.
+
+ Intended usage: Whenever DNS resources are useful to reference by
+ protocol independent identifiers, often when the data is more
+ important than the access method. Since software in general has
+ coped without this so far, it is not anticipated to be implemented
+
+
+
+Josefsson Expires April 25, 2004 [Page 5]
+
+Internet-Draft DNS URI October 2003
+
+
+ widely, nor migrated to by existing systems, but specific solutions
+ (especially security related) may find this appropriate.
+
+ Applications and/or protocols which use this scheme: Security related
+ software. It may be of interest to auxilliary DNS related software
+ too.
+
+ Interoperability considerations: The data referenced by this URI
+ scheme might be transferred by protocols that are not URI aware (such
+ as the DNS protocol). This is not anticipated to have any serious
+ interoperability impact though.
+
+ Interoperability problems may occur if one entity understands a new
+ DNS type or class mnemonic but another entity do not understand it.
+ This is an interoperability problem for DNS software in general,
+ although it is not a major practical problem as the DNS types and
+ classes are fairly static. To guarantee interoperability
+ implementations could use integers for all mnemonics not defined in
+ [2].
+
+ Interaction with Binary Labels [12], or other extended label types,
+ has not been analyzed. However, they appear to be infrequently used
+ in practice.
+
+ Security considerations: See below.
+
+ Contact: simon@josefsson.org
+
+ Author/Change Controller: simon@josefsson.org
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Josefsson Expires April 25, 2004 [Page 6]
+
+Internet-Draft DNS URI October 2003
+
+
+3. Examples
+
+ A DNS URI is of the following general form. This is intended to
+ illustrate, not define, the scheme.
+
+ dns:[//authority/]domain[?type=TYPE;class=CLASS]
+
+ The following illustrate a URI for a resource with the name
+ "www.example.org", the Internet (IN) class and the Address (A) type:
+
+ dns:www.example.org?class=IN;type=A
+
+ Since the default class is IN, and the default type is A, the same
+ resource can be identified by a shorter URI:
+
+ dns:www.example.org
+
+ The following illustrate a URI for a resource with the name
+ "simon.example.org", for the CERT type, in the Internet (IN) class:
+
+ dns:simon.example.org?type=CERT
+
+ The following illustrate a URI for a resource with the name
+ "ftp.example.org", in the Internet (IN) class and the address (A)
+ type, but from the DNS authority 192.168.1.1 instead of the default
+ authority (i.e., when DNS is used, the query is sent to that server):
+
+ dns://192.168.1.1/ftp.example.org?type=A
+
+ The following illustrate a strange, albeit valid, DNS resource. Note
+ the encoding of "." and 0x00, and the use of a named dnsauthority:
+
+ dns://internal-dns.example.org/*.%3f%20%00%2e%25+?type=TXT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Josefsson Expires April 25, 2004 [Page 7]
+
+Internet-Draft DNS URI October 2003
+
+
+4. Security Considerations
+
+ If a DNS URI references domains in the Internet DNS environment, both
+ the URI itself and the information referenced by the URI is public
+ information. If a DNS URI is used within an "internal" DNS
+ environment, both the DNS URI and the data is referenced should be
+ handled using the same considerations that apply to DNS data in the
+ environment.
+
+ If information referenced by DNS URIs are used to make security
+ decisions (examples of such data include, but is not limited to,
+ certificates stored in the DNS), implementations may need to employ
+ security techniques such as Secure DNS [9], or even CMS [15] or
+ OpenPGP [8], to protect the data during transport. How to implement
+ this will depend on the usage scenario, and it is not up to this URI
+ scheme to define how the data referenced by DNS URIs should be
+ protected.
+
+ If applications accept unknown dnsqueryelement values (e.g., accepts
+ the URI "dns:www.example.org?secret=value" without knowing what the
+ "secret=value" dnsqueryelement means), a covert channel used to
+ "leak" information may be enabled. The implications of covert
+ channels should be understood by applications that accepts unknown
+ dnsqueryelement values.
+
+ This draft does not modify the security considerations related to the
+ DNS or URIs in general.
+
+5. IANA Considerations
+
+ The IANA is asked to register the DNS URI scheme, using the template
+ in section 2, in accordance with RFC 2717 [13].
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Josefsson Expires April 25, 2004 [Page 8]
+
+Internet-Draft DNS URI October 2003
+
+
+Acknowledgments
+
+ Thanks to Stuart Cheshire, Donald Eastlake, Pasi Eronen, Ted Hardie,
+ Peter Koch, Andrew Main, Larry Masinter, Michael Mealling, Steve
+ Mattson, and Paul Vixie for comments and suggestions. The author
+ acknowledges the RSA Laboratories for supporting the work that led to
+ this document.
+
+Normative References
+
+ [1] Mockapetris, P., "Domain names - concepts and facilities", STD
+ 13, RFC 1034, November 1987.
+
+ [2] Mockapetris, P., "Domain names - implementation and
+ specification", STD 13, RFC 1035, November 1987.
+
+ [3] Crocker, D. and P. Overell, "Augmented BNF for Syntax
+ Specifications: ABNF", RFC 2234, November 1997.
+
+ [4] Berners-Lee, T., Fielding, R. and L. Masinter, "Uniform Resource
+ Identifiers (URI): Generic Syntax", RFC 2396, August 1998.
+
+Informative References
+
+ [5] Postel, J. and J. Reynolds, "File Transfer Protocol", STD 9,
+ RFC 959, October 1985.
+
+ [6] Bradner, S., "Key words for use in RFCs to Indicate Requirement
+ Levels", BCP 14, RFC 2119, March 1997.
+
+ [7] Yergeau, F., "UTF-8, a transformation format of ISO 10646", RFC
+ 2279, January 1998.
+
+ [8] Callas, J., Donnerhacke, L., Finney, H. and R. Thayer, "OpenPGP
+ Message Format", RFC 2440, November 1998.
+
+ [9] Eastlake, D., "Domain Name System Security Extensions", RFC
+ 2535, March 1999.
+
+ [10] Eastlake, D. and O. Gudmundsson, "Storing Certificates in the
+ Domain Name System (DNS)", RFC 2538, March 1999.
+
+ [11] Myers, M., Ankney, R., Malpani, A., Galperin, S. and C. Adams,
+ "X.509 Internet Public Key Infrastructure Online Certificate
+ Status Protocol - OCSP", RFC 2560, June 1999.
+
+ [12] Crawford, M., "Binary Labels in the Domain Name System", RFC
+ 2673, August 1999.
+
+
+
+Josefsson Expires April 25, 2004 [Page 9]
+
+Internet-Draft DNS URI October 2003
+
+
+ [13] Petke, R. and I. King, "Registration Procedures for URL Scheme
+ Names", BCP 35, RFC 2717, November 1999.
+
+ [14] Connolly, D. and L. Masinter, "The 'text/html' Media Type", RFC
+ 2854, June 2000.
+
+ [15] Housley, R., "Cryptographic Message Syntax (CMS)", RFC 3369,
+ August 2002.
+
+ [16] Faltstrom, P., Hoffman, P. and A. Costello, "Internationalizing
+ Domain Names in Applications (IDNA)", RFC 3490, March 2003.
+
+
+Author's Address
+
+ Simon Josefsson
+
+ EMail: simon@josefsson.org
+
+Appendix A. Revision Changes
+
+ Note to RFC editor: This appendix is to be removed on publication.
+
+A.1 Changes since -06
+
+ The MIME registration templates for text/dns and application/dns was
+ removed, and will be defined in separate documents.
+
+ Improved discussion related to which mnemonics that must be
+ supported. The interoperability problem that provoked the
+ clarification is also mentioned.
+
+ Security consideration improvements.
+
+A.2 Changes since -07
+
+ Author/Change Controller changed to author of this document, not
+ IESG. Terminology section collapsed into introduction. The second
+ paragraph of the introduction rewritten and gives explicit examples.
+ Intended usage and applications fields fixed. Moved this revision
+ tracking information to an appendix. Mention IDN in charset section.
+ All previous thanks to suggestions by Larry Masinter.
+
+A.3 Changes since -08
+
+ Modifications derived from Last-Call comments: Made more clear that
+ DNS URIs does not imply use of the DNS protocol, but the issue is not
+ stressed because of the apparent inflamatory state of affairs. Added
+
+
+
+Josefsson Expires April 25, 2004 [Page 10]
+
+Internet-Draft DNS URI October 2003
+
+
+ informative references to HTML and FTP. Clarified that dnsname can
+ be empty. Clarified that first dnsqueryelement "win" in case of
+ ambiguity. Clarified security consideration with respect to unknown
+ dnsqueryelements. Use "authority" instead of "server". Say "IANA
+ registered" instead of "standard". Interoperability note about binary
+ DNS labels. Typos.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Josefsson Expires April 25, 2004 [Page 11]
+
+Internet-Draft DNS URI October 2003
+
+
+Intellectual Property Statement
+
+ The IETF takes no position regarding the validity or scope of any
+ intellectual property or other rights that might be claimed to
+ pertain to the implementation or use of the technology described in
+ this document or the extent to which any license under such rights
+ might or might not be available; neither does it represent that it
+ has made any effort to identify any such rights. Information on the
+ IETF's procedures with respect to rights in standards-track and
+ standards-related documentation can be found in BCP-11. Copies of
+ claims of rights made available for publication and any assurances of
+ licenses to be made available, or the result of an attempt made to
+ obtain a general license or permission for the use of such
+ proprietary rights by implementors or users of this specification can
+ be obtained from the IETF Secretariat.
+
+ The IETF invites any interested party to bring to its attention any
+ copyrights, patents or patent applications, or other proprietary
+ rights which may cover technology that may be required to practice
+ this standard. Please address the information to the IETF Executive
+ Director.
+
+
+Full Copyright Statement
+
+ Copyright (C) The Internet Society (2003). All Rights Reserved.
+
+ This document and translations of it may be copied and furnished to
+ others, and derivative works that comment on or otherwise explain it
+ or assist in its implementation may be prepared, copied, published
+ and distributed, in whole or in part, without restriction of any
+ kind, provided that the above copyright notice and this paragraph are
+ included on all such copies and derivative works. However, this
+ document itself may not be modified in any way, such as by removing
+ the copyright notice or references to the Internet Society or other
+ Internet organizations, except as needed for the purpose of
+ developing Internet standards in which case the procedures for
+ copyrights defined in the Internet Standards process must be
+ followed, or as required to translate it into languages other than
+ English.
+
+ The limited permissions granted above are perpetual and will not be
+ revoked by the Internet Society or its successors or assignees.
+
+ This document and the information contained herein is provided on an
+ "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING
+ TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
+ BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION
+
+
+
+Josefsson Expires April 25, 2004 [Page 12]
+
+Internet-Draft DNS URI October 2003
+
+
+ HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF
+ MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+
+
+Acknowledgment
+
+ Funding for the RFC Editor function is currently provided by the
+ Internet Society.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Josefsson Expires April 25, 2004 [Page 13]
+
diff --git a/tcllib/modules/dns/dns.tcl b/tcllib/modules/dns/dns.tcl
new file mode 100644
index 0000000..305bd64
--- /dev/null
+++ b/tcllib/modules/dns/dns.tcl
@@ -0,0 +1,1416 @@
+# dns.tcl - Copyright (C) 2002 Pat Thoyts <patthoyts@users.sourceforge.net>
+#
+# Provide a Tcl only Domain Name Service client. See RFC 1034 and RFC 1035
+# for information about the DNS protocol. This should insulate Tcl scripts
+# from problems with using the system library resolver for slow name servers.
+#
+# This implementation uses TCP only for DNS queries. The protocol reccommends
+# that UDP be used in these cases but Tcl does not include UDP sockets by
+# default. The package should be simple to extend to use a TclUDP extension
+# in the future.
+#
+# Support for SPF (http://spf.pobox.com/rfcs.html) will need updating
+# if or when the proposed draft becomes accepted.
+#
+# Support added for RFC1886 - DNS Extensions to support IP version 6
+# Support added for RFC2782 - DNS RR for specifying the location of services
+# Support added for RFC1995 - Incremental Zone Transfer in DNS
+#
+# TODO:
+# - When using tcp we should make better use of the open connection and
+# send multiple queries along the same connection.
+#
+# - We must switch to using TCP for truncated UDP packets.
+#
+# - Read RFC 2136 - dynamic updating of DNS
+#
+# -------------------------------------------------------------------------
+# See the file "license.terms" for information on usage and redistribution
+# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+# -------------------------------------------------------------------------
+
+package require Tcl 8.2; # tcl minimum version
+package require logger; # tcllib 1.3
+package require uri; # tcllib 1.1
+package require uri::urn; # tcllib 1.2
+package require ip; # tcllib 1.7
+
+namespace eval ::dns {
+ namespace export configure resolve name address cname \
+ status reset wait cleanup errorcode
+
+ variable options
+ if {![info exists options]} {
+ array set options {
+ port 53
+ timeout 30000
+ protocol tcp
+ search {}
+ nameserver {localhost}
+ loglevel warn
+ }
+ variable log [logger::init dns]
+ ${log}::setlevel $options(loglevel)
+ }
+
+ # We can use either ceptcl or tcludp for UDP support.
+ if {![catch {package require udp 1.0.4} msg]} { ;# tcludp 1.0.4+
+ # If TclUDP 1.0.4 or better is available, use it.
+ set options(protocol) udp
+ } else {
+ if {![catch {package require ceptcl} msg]} {
+ set options(protocol) udp
+ }
+ }
+
+ variable types
+ array set types {
+ A 1 NS 2 MD 3 MF 4 CNAME 5 SOA 6 MB 7 MG 8 MR 9
+ NULL 10 WKS 11 PTR 12 HINFO 13 MINFO 14 MX 15 TXT 16
+ SPF 16 AAAA 28 SRV 33 IXFR 251 AXFR 252 MAILB 253 MAILA 254
+ ANY 255 * 255
+ }
+
+ variable classes
+ array set classes { IN 1 CS 2 CH 3 HS 4 * 255}
+
+ variable uid
+ if {![info exists uid]} {
+ set uid 0
+ }
+}
+
+# -------------------------------------------------------------------------
+
+# Description:
+# Configure the DNS package. In particular the local nameserver will need
+# to be set. With no options, returns a list of all current settings.
+#
+proc ::dns::configure {args} {
+ variable options
+ variable log
+
+ if {[llength $args] < 1} {
+ set r {}
+ foreach opt [lsort [array names options]] {
+ lappend r -$opt $options($opt)
+ }
+ return $r
+ }
+
+ set cget 0
+ if {[llength $args] == 1} {
+ set cget 1
+ }
+
+ while {[string match -* [lindex $args 0]]} {
+ switch -glob -- [lindex $args 0] {
+ -n* -
+ -ser* {
+ if {$cget} {
+ return $options(nameserver)
+ } else {
+ set options(nameserver) [Pop args 1]
+ }
+ }
+ -po* {
+ if {$cget} {
+ return $options(port)
+ } else {
+ set options(port) [Pop args 1]
+ }
+ }
+ -ti* {
+ if {$cget} {
+ return $options(timeout)
+ } else {
+ set options(timeout) [Pop args 1]
+ }
+ }
+ -pr* {
+ if {$cget} {
+ return $options(protocol)
+ } else {
+ set proto [string tolower [Pop args 1]]
+ if {[string compare udp $proto] == 0 \
+ && [string compare tcp $proto] == 0} {
+ return -code error "invalid protocol \"$proto\":\
+ protocol must be either \"udp\" or \"tcp\""
+ }
+ set options(protocol) $proto
+ }
+ }
+ -sea* {
+ if {$cget} {
+ return $options(search)
+ } else {
+ set options(search) [Pop args 1]
+ }
+ }
+ -log* {
+ if {$cget} {
+ return $options(loglevel)
+ } else {
+ set options(loglevel) [Pop args 1]
+ ${log}::setlevel $options(loglevel)
+ }
+ }
+ -- { Pop args ; break }
+ default {
+ set opts [join [lsort [array names options]] ", -"]
+ return -code error "bad option [lindex $args 0]:\
+ must be one of -$opts"
+ }
+ }
+ Pop args
+ }
+
+ return
+}
+
+# -------------------------------------------------------------------------
+
+# Description:
+# Create a DNS query and send to the specified name server. Returns a token
+# to be used to obtain any further information about this query.
+#
+proc ::dns::resolve {query args} {
+ variable uid
+ variable options
+ variable log
+
+ # get a guaranteed unique and non-present token id.
+ set id [incr uid]
+ while {[info exists [set token [namespace current]::$id]]} {
+ set id [incr uid]
+ }
+ # FRINK: nocheck
+ variable $token
+ upvar 0 $token state
+
+ # Setup token/state defaults.
+ set state(id) $id
+ set state(query) $query
+ set state(qdata) ""
+ set state(opcode) 0; # 0 = query, 1 = inverse query.
+ set state(-type) A; # DNS record type (A address)
+ set state(-class) IN; # IN (internet address space)
+ set state(-recurse) 1; # Recursion Desired
+ set state(-command) {}; # asynchronous handler
+ set state(-timeout) $options(timeout); # connection timeout default.
+ set state(-nameserver) $options(nameserver);# default nameserver
+ set state(-port) $options(port); # default namerservers port
+ set state(-search) $options(search); # domain search list
+ set state(-protocol) $options(protocol); # which protocol udp/tcp
+
+ # Handle DNS URL's
+ if {[string match "dns:*" $query]} {
+ array set URI [uri::split $query]
+ foreach {opt value} [uri::split $query] {
+ if {$value != {} && [info exists state(-$opt)]} {
+ set state(-$opt) $value
+ }
+ }
+ set state(query) $URI(query)
+ ${log}::debug "parsed query: $query"
+ }
+
+ while {[string match -* [lindex $args 0]]} {
+ switch -glob -- [lindex $args 0] {
+ -n* - ns -
+ -ser* { set state(-nameserver) [Pop args 1] }
+ -po* { set state(-port) [Pop args 1] }
+ -ti* { set state(-timeout) [Pop args 1] }
+ -co* { set state(-command) [Pop args 1] }
+ -cl* { set state(-class) [Pop args 1] }
+ -ty* { set state(-type) [Pop args 1] }
+ -pr* { set state(-protocol) [Pop args 1] }
+ -sea* { set state(-search) [Pop args 1] }
+ -re* { set state(-recurse) [Pop args 1] }
+ -inv* { set state(opcode) 1 }
+ -status {set state(opcode) 2}
+ -data { set state(qdata) [Pop args 1] }
+ default {
+ set opts [join [lsort [array names state -*]] ", "]
+ return -code error "bad option [lindex $args 0]: \
+ must be $opts"
+ }
+ }
+ Pop args
+ }
+
+ if {$state(-nameserver) == {}} {
+ return -code error "no nameserver specified"
+ }
+
+ if {$state(-protocol) == "udp"} {
+ if {[llength [package provide ceptcl]] == 0 \
+ && [llength [package provide udp]] == 0} {
+ return -code error "udp support is not available,\
+ get ceptcl or tcludp"
+ }
+ }
+
+ # Check for reverse lookups
+ if {[regexp {^(?:\d{0,3}\.){3}\d{0,3}$} $state(query)]} {
+ set addr [lreverse [split $state(query) .]]
+ lappend addr in-addr arpa
+ set state(query) [join $addr .]
+ set state(-type) PTR
+ }
+
+ BuildMessage $token
+
+ if {$state(-protocol) == "tcp"} {
+ TcpTransmit $token
+ } else {
+ UdpTransmit $token
+ }
+ if {$state(-command) == {}} {
+ wait $token
+ }
+ return $token
+}
+
+# -------------------------------------------------------------------------
+
+# Description:
+# Return a list of domain names returned as results for the last query.
+#
+proc ::dns::name {token} {
+ set r {}
+ Flags $token flags
+ array set reply [Decode $token]
+
+ switch -exact -- $flags(opcode) {
+ 0 {
+ # QUERY
+ foreach answer $reply(AN) {
+ array set AN $answer
+ if {![info exists AN(type)]} {set AN(type) {}}
+ switch -exact -- $AN(type) {
+ MX - NS - PTR {
+ if {[info exists AN(rdata)]} {lappend r $AN(rdata)}
+ }
+ default {
+ if {[info exists AN(name)]} {
+ lappend r $AN(name)
+ }
+ }
+ }
+ }
+ }
+
+ 1 {
+ # IQUERY
+ foreach answer $reply(QD) {
+ array set QD $answer
+ lappend r $QD(name)
+ }
+ }
+ default {
+ return -code error "not supported for this query type"
+ }
+ }
+ return $r
+}
+
+# Description:
+# Return a list of the IP addresses returned for this query.
+#
+proc ::dns::address {token} {
+ set r {}
+ array set reply [Decode $token]
+ foreach answer $reply(AN) {
+ array set AN $answer
+
+ if {[info exists AN(type)]} {
+ switch -exact -- $AN(type) {
+ "A" {
+ lappend r $AN(rdata)
+ }
+ "AAAA" {
+ lappend r $AN(rdata)
+ }
+ }
+ }
+ }
+ return $r
+}
+
+# Description:
+# Return a list of all CNAME results returned for this query.
+#
+proc ::dns::cname {token} {
+ set r {}
+ array set reply [Decode $token]
+ foreach answer $reply(AN) {
+ array set AN $answer
+
+ if {[info exists AN(type)]} {
+ if {$AN(type) == "CNAME"} {
+ lappend r $AN(rdata)
+ }
+ }
+ }
+ return $r
+}
+
+# Description:
+# Return the decoded answer records. This can be used for more complex
+# queries where the answer isn't supported byb cname/address/name.
+proc ::dns::result {token args} {
+ array set reply [eval [linsert $args 0 Decode $token]]
+ return $reply(AN)
+}
+
+# -------------------------------------------------------------------------
+
+# Description:
+# Get the status of the request.
+#
+proc ::dns::status {token} {
+ upvar #0 $token state
+ return $state(status)
+}
+
+# Description:
+# Get the error message. Empty if no error.
+#
+proc ::dns::error {token} {
+ upvar #0 $token state
+ if {[info exists state(error)]} {
+ return $state(error)
+ }
+ return ""
+}
+
+# Description
+# Get the error code. This is 0 for a successful transaction.
+#
+proc ::dns::errorcode {token} {
+ upvar #0 $token state
+ set flags [Flags $token]
+ set ndx [lsearch -exact $flags errorcode]
+ incr ndx
+ return [lindex $flags $ndx]
+}
+
+# Description:
+# Reset a connection with optional reason.
+#
+proc ::dns::reset {token {why reset} {errormsg {}}} {
+ upvar #0 $token state
+ set state(status) $why
+ if {[string length $errormsg] > 0 && ![info exists state(error)]} {
+ set state(error) $errormsg
+ }
+ catch {fileevent $state(sock) readable {}}
+ Finish $token
+}
+
+# Description:
+# Wait for a request to complete and return the status.
+#
+proc ::dns::wait {token} {
+ upvar #0 $token state
+
+ if {$state(status) == "connect"} {
+ vwait [subst $token](status)
+ }
+
+ return $state(status)
+}
+
+# Description:
+# Remove any state associated with this token.
+#
+proc ::dns::cleanup {token} {
+ upvar #0 $token state
+ if {[info exists state]} {
+ catch {close $state(sock)}
+ catch {after cancel $state(after)}
+ unset state
+ }
+}
+
+# -------------------------------------------------------------------------
+
+# Description:
+# Dump the raw data of the request and reply packets.
+#
+proc ::dns::dump {args} {
+ if {[llength $args] == 1} {
+ set type -reply
+ set token [lindex $args 0]
+ } elseif { [llength $args] == 2 } {
+ set type [lindex $args 0]
+ set token [lindex $args 1]
+ } else {
+ return -code error "wrong # args:\
+ should be \"dump ?option? methodName\""
+ }
+
+ # FRINK: nocheck
+ variable $token
+ upvar 0 $token state
+
+ set result {}
+ switch -glob -- $type {
+ -qu* -
+ -req* {
+ set result [DumpMessage $state(request)]
+ }
+ -rep* {
+ set result [DumpMessage $state(reply)]
+ }
+ default {
+ error "unrecognised option: must be one of \
+ \"-query\", \"-request\" or \"-reply\""
+ }
+ }
+
+ return $result
+}
+
+# Description:
+# Perform a hex dump of binary data.
+#
+proc ::dns::DumpMessage {data} {
+ set result {}
+ binary scan $data c* r
+ foreach c $r {
+ append result [format "%02x " [expr {$c & 0xff}]]
+ }
+ return $result
+}
+
+# -------------------------------------------------------------------------
+
+# Description:
+# Contruct a DNS query packet.
+#
+proc ::dns::BuildMessage {token} {
+ # FRINK: nocheck
+ variable $token
+ upvar 0 $token state
+ variable types
+ variable classes
+ variable options
+
+ if {! [info exists types($state(-type))] } {
+ return -code error "invalid DNS query type"
+ }
+
+ if {! [info exists classes($state(-class))] } {
+ return -code error "invalid DNS query class"
+ }
+
+ set qdcount 0
+ set qsection {}
+ set nscount 0
+ set nsdata {}
+
+ # In theory we can send multiple queries. In practice, named doesn't
+ # appear to like that much. If it did work we'd do this:
+ # foreach domain [linsert $options(search) 0 {}] ...
+
+
+ # Pack the query: QNAME QTYPE QCLASS
+ set qsection [PackName $state(query)]
+ append qsection [binary format SS \
+ $types($state(-type))\
+ $classes($state(-class))]
+ incr qdcount
+
+ if {[string length $state(qdata)] > 0} {
+ set nsdata [eval [linsert $state(qdata) 0 PackRecord]]
+ incr nscount
+ }
+
+ switch -exact -- $state(opcode) {
+ 0 {
+ # QUERY
+ set state(request) [binary format SSSSSS $state(id) \
+ [expr {($state(opcode) << 11) | ($state(-recurse) << 8)}] \
+ $qdcount 0 $nscount 0]
+ append state(request) $qsection $nsdata
+ }
+ 1 {
+ # IQUERY
+ set state(request) [binary format SSSSSS $state(id) \
+ [expr {($state(opcode) << 11) | ($state(-recurse) << 8)}] \
+ 0 $qdcount 0 0 0]
+ append state(request) \
+ [binary format cSSI 0 \
+ $types($state(-type)) $classes($state(-class)) 0]
+ switch -exact -- $state(-type) {
+ A {
+ append state(request) \
+ [binary format Sc4 4 [split $state(query) .]]
+ }
+ PTR {
+ append state(request) \
+ [binary format Sc4 4 [split $state(query) .]]
+ }
+ default {
+ return -code error "inverse query not supported for this type"
+ }
+ }
+ }
+ default {
+ return -code error "operation not supported"
+ }
+ }
+
+ return
+}
+
+# Pack a human readable dns name into a DNS resource record format.
+proc ::dns::PackName {name} {
+ set data ""
+ foreach part [split [string trim $name .] .] {
+ set len [string length $part]
+ append data [binary format ca$len $len $part]
+ }
+ append data \x00
+ return $data
+}
+
+# Pack a character string - byte length prefixed
+proc ::dns::PackString {text} {
+ set len [string length $text]
+ set data [binary format ca$len $len $text]
+ return $data
+}
+
+# Pack up a single DNS resource record. See RFC1035: 3.2 for the format
+# of each type.
+# eg: PackRecord name wiki.tcl.tk type MX class IN rdata {10 mail.example.com}
+#
+proc ::dns::PackRecord {args} {
+ variable types
+ variable classes
+ array set rr {name "" type A class IN ttl 0 rdlength 0 rdata ""}
+ array set rr $args
+ set data [PackName $rr(name)]
+
+ switch -exact -- $rr(type) {
+ CNAME - MB - MD - MF - MG - MR - NS - PTR {
+ set rr(rdata) [PackName $rr(rdata)]
+ }
+ HINFO {
+ array set r {CPU {} OS {}}
+ array set r $rr(rdata)
+ set rr(rdata) [PackString $r(CPU)]
+ append rr(rdata) [PackString $r(OS)]
+ }
+ MINFO {
+ array set r {RMAILBX {} EMAILBX {}}
+ array set r $rr(rdata)
+ set rr(rdata) [PackString $r(RMAILBX)]
+ append rr(rdata) [PackString $r(EMAILBX)]
+ }
+ MX {
+ foreach {pref exch} $rr(rdata) break
+ set rr(rdata) [binary format S $pref]
+ append rr(rdata) [PackName $exch]
+ }
+ TXT {
+ set str $rr(rdata)
+ set len [string length [set str $rr(rdata)]]
+ set rr(rdata) ""
+ for {set n 0} {$n < $len} {incr n} {
+ set s [string range $str $n [incr n 253]]
+ append rr(rdata) [PackString $s]
+ }
+ }
+ NULL {}
+ SOA {
+ array set r {MNAME {} RNAME {}
+ SERIAL 0 REFRESH 0 RETRY 0 EXPIRE 0 MINIMUM 0}
+ array set r $rr(rdata)
+ set rr(rdata) [PackName $r(MNAME)]
+ append rr(rdata) [PackName $r(RNAME)]
+ append rr(rdata) [binary format IIIII $r(SERIAL) \
+ $r(REFRESH) $r(RETRY) $r(EXPIRE) $r(MINIMUM)]
+ }
+ }
+
+ # append the root label and the type flag and query class.
+ append data [binary format SSIS $types($rr(type)) \
+ $classes($rr(class)) $rr(ttl) [string length $rr(rdata)]]
+ append data $rr(rdata)
+ return $data
+}
+
+# -------------------------------------------------------------------------
+
+# Description:
+# Transmit a DNS request over a tcp connection.
+#
+proc ::dns::TcpTransmit {token} {
+ # FRINK: nocheck
+ variable $token
+ upvar 0 $token state
+
+ # setup the timeout
+ if {$state(-timeout) > 0} {
+ set state(after) [after $state(-timeout) \
+ [list [namespace origin reset] \
+ $token timeout\
+ "operation timed out"]]
+ }
+
+ # Sometimes DNS servers drop TCP requests. So it's better to
+ # use asynchronous connect
+ set s [socket -async $state(-nameserver) $state(-port)]
+ fileevent $s writable [list [namespace origin TcpConnected] $token $s]
+ set state(sock) $s
+ set state(status) connect
+
+ return $token
+}
+
+proc ::dns::TcpConnected {token s} {
+ variable $token
+ upvar 0 $token state
+
+ fileevent $s writable {}
+ if {[catch {fconfigure $s -peername}]} {
+ # TCP connection failed
+ Finish $token "can't connect to server"
+ return
+ }
+
+ fconfigure $s -blocking 0 -translation binary -buffering none
+
+ # For TCP the message must be prefixed with a 16bit length field.
+ set req [binary format S [string length $state(request)]]
+ append req $state(request)
+
+ puts -nonewline $s $req
+
+ fileevent $s readable [list [namespace current]::TcpEvent $token]
+}
+
+# -------------------------------------------------------------------------
+# Description:
+# Transmit a DNS request using UDP datagrams
+#
+# Note:
+# This requires a UDP implementation that can transmit binary data.
+# As yet I have been unable to test this myself and the tcludp package
+# cannot do this.
+#
+proc ::dns::UdpTransmit {token} {
+ # FRINK: nocheck
+ variable $token
+ upvar 0 $token state
+
+ # setup the timeout
+ if {$state(-timeout) > 0} {
+ set state(after) [after $state(-timeout) \
+ [list [namespace origin reset] \
+ $token timeout\
+ "operation timed out"]]
+ }
+
+ if {[llength [package provide ceptcl]] > 0} {
+ # using ceptcl
+ set state(sock) [cep -type datagram $state(-nameserver) $state(-port)]
+ fconfigure $state(sock) -blocking 0
+ } else {
+ # using tcludp
+ set state(sock) [udp_open]
+ udp_conf $state(sock) $state(-nameserver) $state(-port)
+ }
+ fconfigure $state(sock) -translation binary -buffering none
+ set state(status) connect
+ puts -nonewline $state(sock) $state(request)
+
+ fileevent $state(sock) readable [list [namespace current]::UdpEvent $token]
+
+ return $token
+}
+
+# -------------------------------------------------------------------------
+
+# Description:
+# Tidy up after a tcp transaction.
+#
+proc ::dns::Finish {token {errormsg ""}} {
+ # FRINK: nocheck
+ variable $token
+ upvar 0 $token state
+ global errorInfo errorCode
+
+ if {[string length $errormsg] != 0} {
+ set state(error) $errormsg
+ set state(status) error
+ }
+ catch {close $state(sock)}
+ catch {after cancel $state(after)}
+ if {[info exists state(-command)] && $state(-command) != {}} {
+ if {[catch {eval $state(-command) {$token}} err]} {
+ if {[string length $errormsg] == 0} {
+ set state(error) [list $err $errorInfo $errorCode]
+ set state(status) error
+ }
+ }
+ if {[info exists state(-command)]} {
+ unset state(-command)
+ }
+ }
+}
+
+# -------------------------------------------------------------------------
+
+# Description:
+# Handle end-of-file on a tcp connection.
+#
+proc ::dns::Eof {token} {
+ # FRINK: nocheck
+ variable $token
+ upvar 0 $token state
+ set state(status) eof
+ Finish $token
+}
+
+# -------------------------------------------------------------------------
+
+# Description:
+# Process a DNS reply packet (protocol independent)
+#
+proc ::dns::Receive {token} {
+ # FRINK: nocheck
+ variable $token
+ upvar 0 $token state
+
+ binary scan $state(reply) SS id flags
+ set status [expr {$flags & 0x000F}]
+
+ switch -- $status {
+ 0 {
+ set state(status) ok
+ Finish $token
+ }
+ 1 { Finish $token "Format error - unable to interpret the query." }
+ 2 { Finish $token "Server failure - internal server error." }
+ 3 { Finish $token "Name Error - domain does not exist" }
+ 4 { Finish $token "Not implemented - the query type is not available." }
+ 5 { Finish $token "Refused - your request has been refused by the server." }
+ default {
+ Finish $token "unrecognised error code: $err"
+ }
+ }
+}
+
+# -------------------------------------------------------------------------
+
+# Description:
+# file event handler for tcp socket. Wait for the reply data.
+#
+proc ::dns::TcpEvent {token} {
+ variable log
+ # FRINK: nocheck
+ variable $token
+ upvar 0 $token state
+ set s $state(sock)
+
+ if {[eof $s]} {
+ Eof $token
+ return
+ }
+
+ set status [catch {read $state(sock)} result]
+ if {$status != 0} {
+ ${log}::debug "Event error: $result"
+ Finish $token "error reading data: $result"
+ } elseif { [string length $result] >= 0 } {
+ if {[catch {
+ # Handle incomplete reads - check the size and keep reading.
+ if {![info exists state(size)]} {
+ binary scan $result S state(size)
+ set result [string range $result 2 end]
+ }
+ append state(reply) $result
+
+ # check the length and flags and chop off the tcp length prefix.
+ if {[string length $state(reply)] >= $state(size)} {
+ binary scan $result S id
+ set id [expr {$id & 0xFFFF}]
+ if {$id != [expr {$state(id) & 0xFFFF}]} {
+ ${log}::error "received packed with incorrect id"
+ }
+ # bug #1158037 - doing this causes problems > 65535 requests!
+ #Receive [namespace current]::$id
+ Receive $token
+ } else {
+ ${log}::debug "Incomplete tcp read:\
+ [string length $state(reply)] should be $state(size)"
+ }
+ } err]} {
+ Finish $token "Event error: $err"
+ }
+ } elseif { [eof $state(sock)] } {
+ Eof $token
+ } elseif { [fblocked $state(sock)] } {
+ ${log}::debug "Event blocked"
+ } else {
+ ${log}::critical "Event error: this can't happen!"
+ Finish $token "Event error: this can't happen!"
+ }
+}
+
+# -------------------------------------------------------------------------
+
+# Description:
+# file event handler for udp sockets.
+proc ::dns::UdpEvent {token} {
+ # FRINK: nocheck
+ variable $token
+ upvar 0 $token state
+ set s $state(sock)
+
+ set payload [read $state(sock)]
+ append state(reply) $payload
+
+ binary scan $payload S id
+ set id [expr {$id & 0xFFFF}]
+ if {$id != [expr {$state(id) & 0xFFFF}]} {
+ ${log}::error "received packed with incorrect id"
+ }
+ # bug #1158037 - doing this causes problems > 65535 requests!
+ #Receive [namespace current]::$id
+ Receive $token
+}
+
+# -------------------------------------------------------------------------
+
+proc ::dns::Flags {token {varname {}}} {
+ # FRINK: nocheck
+ variable $token
+ upvar 0 $token state
+
+ if {$varname != {}} {
+ upvar $varname flags
+ }
+
+ array set flags {query 0 opcode 0 authoritative 0 errorcode 0
+ truncated 0 recursion_desired 0 recursion_allowed 0}
+
+ binary scan $state(reply) SSSSSS mid hdr nQD nAN nNS nAR
+
+ set flags(response) [expr {($hdr & 0x8000) >> 15}]
+ set flags(opcode) [expr {($hdr & 0x7800) >> 11}]
+ set flags(authoritative) [expr {($hdr & 0x0400) >> 10}]
+ set flags(truncated) [expr {($hdr & 0x0200) >> 9}]
+ set flags(recursion_desired) [expr {($hdr & 0x0100) >> 8}]
+ set flags(recursion_allowed) [expr {($hdr & 0x0080) >> 7}]
+ set flags(errorcode) [expr {($hdr & 0x000F)}]
+
+ return [array get flags]
+}
+
+# -------------------------------------------------------------------------
+
+# Description:
+# Decode a DNS packet (either query or response).
+#
+proc ::dns::Decode {token args} {
+ variable log
+ # FRINK: nocheck
+ variable $token
+ upvar 0 $token state
+
+ array set opts {-rdata 0 -query 0}
+ while {[string match -* [set option [lindex $args 0]]]} {
+ switch -exact -- $option {
+ -rdata { set opts(-rdata) 1 }
+ -query { set opts(-query) 1 }
+ default {
+ return -code error "bad option \"$option\":\
+ must be -rdata"
+ }
+ }
+ Pop args
+ }
+
+ if {$opts(-query)} {
+ binary scan $state(request) SSSSSSc* mid hdr nQD nAN nNS nAR data
+ } else {
+ binary scan $state(reply) SSSSSSc* mid hdr nQD nAN nNS nAR data
+ }
+
+ set fResponse [expr {($hdr & 0x8000) >> 15}]
+ set fOpcode [expr {($hdr & 0x7800) >> 11}]
+ set fAuthoritative [expr {($hdr & 0x0400) >> 10}]
+ set fTrunc [expr {($hdr & 0x0200) >> 9}]
+ set fRecurse [expr {($hdr & 0x0100) >> 8}]
+ set fCanRecurse [expr {($hdr & 0x0080) >> 7}]
+ set fRCode [expr {($hdr & 0x000F)}]
+ set flags ""
+
+ if {$fResponse} {set flags "QR"} else {set flags "Q"}
+ set opcodes [list QUERY IQUERY STATUS]
+ lappend flags [lindex $opcodes $fOpcode]
+ if {$fAuthoritative} {lappend flags "AA"}
+ if {$fTrunc} {lappend flags "TC"}
+ if {$fRecurse} {lappend flags "RD"}
+ if {$fCanRecurse} {lappend flags "RA"}
+
+ set info "ID: $mid\
+ Fl: [format 0x%02X [expr {$hdr & 0xFFFF}]] ($flags)\
+ NQ: $nQD\
+ NA: $nAN\
+ NS: $nNS\
+ AR: $nAR"
+ ${log}::debug $info
+
+ set ndx 12
+ set r {}
+ set QD [ReadQuestion $nQD $state(reply) ndx]
+ lappend r QD $QD
+ set AN [ReadAnswer $nAN $state(reply) ndx $opts(-rdata)]
+ lappend r AN $AN
+ set NS [ReadAnswer $nNS $state(reply) ndx $opts(-rdata)]
+ lappend r NS $NS
+ set AR [ReadAnswer $nAR $state(reply) ndx $opts(-rdata)]
+ lappend r AR $AR
+ return $r
+}
+
+# -------------------------------------------------------------------------
+
+proc ::dns::Expand {data} {
+ set r {}
+ binary scan $data c* d
+ foreach c $d {
+ lappend r [expr {$c & 0xFF}]
+ }
+ return $r
+}
+
+
+# -------------------------------------------------------------------------
+# Description:
+# Pop the nth element off a list. Used in options processing.
+#
+proc ::dns::Pop {varname {nth 0}} {
+ upvar $varname args
+ set r [lindex $args $nth]
+ set args [lreplace $args $nth $nth]
+ return $r
+}
+
+# -------------------------------------------------------------------------
+# Description:
+# Reverse a list. Code from http://wiki.tcl.tk/tcl/43
+#
+proc ::dns::lreverse {lst} {
+ set res {}
+ set i [llength $lst]
+ while {$i} {lappend res [lindex $lst [incr i -1]]}
+ return $res
+}
+
+# -------------------------------------------------------------------------
+
+proc ::dns::KeyOf {arrayname value {default {}}} {
+ upvar $arrayname array
+ set lst [array get array]
+ set ndx [lsearch -exact $lst $value]
+ if {$ndx != -1} {
+ incr ndx -1
+ set r [lindex $lst $ndx]
+ } else {
+ set r $default
+ }
+ return $r
+}
+
+
+# -------------------------------------------------------------------------
+# Read the question section from a DNS message. This always starts at index
+# 12 of a message but may be of variable length.
+#
+proc ::dns::ReadQuestion {nitems data indexvar} {
+ variable types
+ variable classes
+ upvar $indexvar index
+ set result {}
+
+ for {set cn 0} {$cn < $nitems} {incr cn} {
+ set r {}
+ lappend r name [ReadName data $index offset]
+ incr index $offset
+
+ # Read off QTYPE and QCLASS for this query.
+ set ndx $index
+ incr index 3
+ binary scan [string range $data $ndx $index] SS qtype qclass
+ set qtype [expr {$qtype & 0xFFFF}]
+ set qclass [expr {$qclass & 0xFFFF}]
+ incr index
+ lappend r type [KeyOf types $qtype $qtype] \
+ class [KeyOf classes $qclass $qclass]
+ lappend result $r
+ }
+ return $result
+}
+
+# -------------------------------------------------------------------------
+
+# Read an answer section from a DNS message.
+#
+proc ::dns::ReadAnswer {nitems data indexvar {raw 0}} {
+ variable types
+ variable classes
+ upvar $indexvar index
+ set result {}
+
+ for {set cn 0} {$cn < $nitems} {incr cn} {
+ set r {}
+ lappend r name [ReadName data $index offset]
+ incr index $offset
+
+ # Read off TYPE, CLASS, TTL and RDLENGTH
+ binary scan [string range $data $index end] SSIS type class ttl rdlength
+
+ set type [expr {$type & 0xFFFF}]
+ set type [KeyOf types $type $type]
+
+ set class [expr {$class & 0xFFFF}]
+ set class [KeyOf classes $class $class]
+
+ set ttl [expr {$ttl & 0xFFFFFFFF}]
+ set rdlength [expr {$rdlength & 0xFFFF}]
+ incr index 10
+ set rdata [string range $data $index [expr {$index + $rdlength - 1}]]
+
+ if {! $raw} {
+ switch -- $type {
+ A {
+ set rdata [join [Expand $rdata] .]
+ }
+ AAAA {
+ set rdata [ip::contract [ip::ToString $rdata]]
+ }
+ NS - CNAME - PTR {
+ set rdata [ReadName data $index off]
+ }
+ MX {
+ binary scan $rdata S preference
+ set exchange [ReadName data [expr {$index + 2}] off]
+ set rdata [list $preference $exchange]
+ }
+ SRV {
+ set x $index
+ set rdata [list priority [ReadUShort data $x off]]
+ incr x $off
+ lappend rdata weight [ReadUShort data $x off]
+ incr x $off
+ lappend rdata port [ReadUShort data $x off]
+ incr x $off
+ lappend rdata target [ReadName data $x off]
+ incr x $off
+ }
+ TXT {
+ set rdata [ReadString data $index $rdlength]
+ }
+ SOA {
+ set x $index
+ set rdata [list MNAME [ReadName data $x off]]
+ incr x $off
+ lappend rdata RNAME [ReadName data $x off]
+ incr x $off
+ lappend rdata SERIAL [ReadULong data $x off]
+ incr x $off
+ lappend rdata REFRESH [ReadLong data $x off]
+ incr x $off
+ lappend rdata RETRY [ReadLong data $x off]
+ incr x $off
+ lappend rdata EXPIRE [ReadLong data $x off]
+ incr x $off
+ lappend rdata MINIMUM [ReadULong data $x off]
+ incr x $off
+ }
+ }
+ }
+
+ incr index $rdlength
+ lappend r type $type class $class ttl $ttl rdlength $rdlength rdata $rdata
+ lappend result $r
+ }
+ return $result
+}
+
+
+# Read a 32bit integer from a DNS packet. These are compatible with
+# the ReadName proc. Additionally - ReadULong takes measures to ensure
+# the unsignedness of the value obtained.
+#
+proc ::dns::ReadLong {datavar index usedvar} {
+ upvar $datavar data
+ upvar $usedvar used
+ set r {}
+ set used 0
+ if {[binary scan $data @${index}I r]} {
+ set used 4
+ }
+ return $r
+}
+
+proc ::dns::ReadULong {datavar index usedvar} {
+ upvar $datavar data
+ upvar $usedvar used
+ set r {}
+ set used 0
+ if {[binary scan $data @${index}cccc b1 b2 b3 b4]} {
+ set used 4
+ # This gets us an unsigned value.
+ set r [expr {($b4 & 0xFF) + (($b3 & 0xFF) << 8)
+ + (($b2 & 0xFF) << 16) + ($b1 << 24)}]
+ }
+ return $r
+}
+
+proc ::dns::ReadUShort {datavar index usedvar} {
+ upvar $datavar data
+ upvar $usedvar used
+ set r {}
+ set used 0
+ if {[binary scan [string range $data $index end] cc b1 b2]} {
+ set used 2
+ # This gets us an unsigned value.
+ set r [expr {(($b2 & 0xff) + (($b1 & 0xff) << 8)) & 0xffff}]
+ }
+ return $r
+}
+
+# Read off the NAME or QNAME element. This reads off each label in turn,
+# dereferencing pointer labels until we have finished. The length of data
+# used is passed back using the usedvar variable.
+#
+proc ::dns::ReadName {datavar index usedvar} {
+ upvar $datavar data
+ upvar $usedvar used
+ set startindex $index
+
+ set r {}
+ set len 1
+ set max [string length $data]
+
+ while {$len != 0 && $index < $max} {
+ # Read the label length (and preread the pointer offset)
+ binary scan [string range $data $index end] cc len lenb
+ set len [expr {$len & 0xFF}]
+ incr index
+
+ if {$len != 0} {
+ if {[expr {$len & 0xc0}]} {
+ binary scan [binary format cc [expr {$len & 0x3f}] [expr {$lenb & 0xff}]] S offset
+ incr index
+ lappend r [ReadName data $offset junk]
+ set len 0
+ } else {
+ lappend r [string range $data $index [expr {$index + $len - 1}]]
+ incr index $len
+ }
+ }
+ }
+ set used [expr {$index - $startindex}]
+ return [join $r .]
+}
+
+proc ::dns::ReadString {datavar index length} {
+ upvar $datavar data
+ set startindex $index
+
+ set r {}
+ set max [expr {$index + $length}]
+
+ while {$index < $max} {
+ binary scan [string range $data $index end] c len
+ set len [expr {$len & 0xFF}]
+ incr index
+
+ if {$len != 0} {
+ append r [string range $data $index [expr {$index + $len - 1}]]
+ incr index $len
+ }
+ }
+ return $r
+}
+
+# -------------------------------------------------------------------------
+
+# Support for finding the local nameservers
+#
+# For unix we can just parse the /etc/resolv.conf if it exists.
+# Of course, some unices use /etc/resolver and other things (NIS for instance)
+# On Windows, we can examine the Internet Explorer settings from the registry.
+#
+switch -exact $::tcl_platform(platform) {
+ windows {
+ proc ::dns::nameservers {} {
+ package require registry
+ set base {HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services}
+ set param "$base\\Tcpip\\Parameters"
+ set interfaces "$param\\Interfaces"
+ set nameservers {}
+ if {[string equal $::tcl_platform(os) "Windows NT"]} {
+ AppendRegistryValue $param NameServer nameservers
+ AppendRegistryValue $param DhcpNameServer nameservers
+ foreach i [registry keys $interfaces] {
+ AppendRegistryValue "$interfaces\\$i" NameServer nameservers
+ AppendRegistryValue "$interfaces\\$i" DhcpNameServer nameservers
+ }
+ } else {
+ set param "$base\\VxD\\MSTCP"
+ AppendRegistryValue $param NameServer nameservers
+ }
+ return $nameservers
+ }
+ proc ::dns::AppendRegistryValue {key val listName} {
+ upvar $listName lst
+ if {![catch {registry get $key $val} v]} {
+ foreach ns [split $v ", "] {
+ if {[lsearch -exact $lst $ns] == -1} {
+ lappend lst $ns
+ }
+ }
+ }
+ }
+ }
+ unix {
+ proc ::dns::nameservers {} {
+ set nameservers {}
+ if {[file readable /etc/resolv.conf]} {
+ set f [open /etc/resolv.conf r]
+ while {![eof $f]} {
+ gets $f line
+ if {[regexp {^\s*nameserver\s+(.*)$} $line -> ns]} {
+ lappend nameservers $ns
+ }
+ }
+ close $f
+ }
+ if {[llength $nameservers] < 1} {
+ lappend nameservers 127.0.0.1
+ }
+ return $nameservers
+ }
+ }
+ default {
+ proc ::dns::nameservers {} {
+ return -code error "command not supported for this platform."
+ }
+ }
+}
+
+# -------------------------------------------------------------------------
+# Possible support for the DNS URL scheme.
+# Ref: http://www.ietf.org/internet-drafts/draft-josefsson-dns-url-04.txt
+# eg: dns:target?class=IN;type=A
+# dns://nameserver/target?type=A
+#
+# URI quoting to be accounted for.
+#
+
+catch {
+ uri::register {dns} {
+ variable escape [set [namespace parent [namespace current]]::basic::escape]
+ variable host [set [namespace parent [namespace current]]::basic::host]
+ variable hostOrPort [set [namespace parent [namespace current]]::basic::hostOrPort]
+
+ variable class [string map {* \\\\*} \
+ "class=([join [array names ::dns::classes] {|}])"]
+ variable type [string map {* \\\\*} \
+ "type=([join [array names ::dns::types] {|}])"]
+ variable classOrType "(?:${class}|${type})"
+ variable classOrTypeSpec "(?:${class}|${type})(?:;(?:${class}|${type}))?"
+
+ variable query "${host}(${classOrTypeSpec})?"
+ variable schemepart "(//${hostOrPort}/)?(${query})"
+ variable url "dns:$schemepart"
+ }
+}
+
+namespace eval ::uri {} ;# needed for pkg_mkIndex.
+
+proc ::uri::SplitDns {uri} {
+ upvar \#0 [namespace current]::dns::schemepart schemepart
+ upvar \#0 [namespace current]::dns::class classOrType
+ upvar \#0 [namespace current]::dns::class classRE
+ upvar \#0 [namespace current]::dns::type typeRE
+ upvar \#0 [namespace current]::dns::classOrTypeSpec classOrTypeSpec
+
+ array set parts {nameserver {} query {} class {} type {} port {}}
+
+ # validate the uri
+ if {[regexp -- $dns::schemepart $uri r] == 1} {
+
+ # deal with the optional class and type specifiers
+ if {[regexp -indices -- "${classOrTypeSpec}$" $uri range]} {
+ set spec [string range $uri [lindex $range 0] [lindex $range 1]]
+ set uri [string range $uri 0 [expr {[lindex $range 0] - 2}]]
+
+ if {[regexp -- "$classRE" $spec -> class]} {
+ set parts(class) $class
+ }
+ if {[regexp -- "$typeRE" $spec -> type]} {
+ set parts(type) $type
+ }
+ }
+
+ # Handle the nameserver specification
+ if {[string match "//*" $uri]} {
+ set uri [string range $uri 2 end]
+ array set tmp [GetHostPort uri]
+ set parts(nameserver) $tmp(host)
+ set parts(port) $tmp(port)
+ }
+
+ # what's left is the query domain name.
+ set parts(query) [string trimleft $uri /]
+ }
+
+ return [array get parts]
+}
+
+proc ::uri::JoinDns {args} {
+ array set parts {nameserver {} port {} query {} class {} type {}}
+ array set parts $args
+ set query [::uri::urn::quote $parts(query)]
+ if {$parts(type) != {}} {
+ append query "?type=$parts(type)"
+ }
+ if {$parts(class) != {}} {
+ if {$parts(type) == {}} {
+ append query "?class=$parts(class)"
+ } else {
+ append query ";class=$parts(class)"
+ }
+ }
+ if {$parts(nameserver) != {}} {
+ set ns "$parts(nameserver)"
+ if {$parts(port) != {}} {
+ append ns ":$parts(port)"
+ }
+ set query "//${ns}/${query}"
+ }
+ return "dns:$query"
+}
+
+# -------------------------------------------------------------------------
+
+catch {dns::configure -nameserver [lindex [dns::nameservers] 0]}
+
+package provide dns 1.3.5
+
+# -------------------------------------------------------------------------
+# Local Variables:
+# indent-tabs-mode: nil
+# End:
diff --git a/tcllib/modules/dns/dns.test b/tcllib/modules/dns/dns.test
new file mode 100644
index 0000000..1e80944
--- /dev/null
+++ b/tcllib/modules/dns/dns.test
@@ -0,0 +1,73 @@
+# dns.test - Copyright (C) 2002 Pat Thoyts <patthoyts@users.sourceforge.net>
+#
+# Tests for the Tcllib dns package
+#
+# -------------------------------------------------------------------------
+# See the file "license.terms" for information on usage and redistribution
+# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+# -------------------------------------------------------------------------
+# RCS: @(#) $Id: dns.test,v 1.6 2006/10/09 21:41:40 andreas_kupries Exp $
+
+# -------------------------------------------------------------------------
+
+source [file join \
+ [file dirname [file dirname [file join [pwd] [info script]]]] \
+ devtools testutilities.tcl]
+
+testsNeedTcl 8.2
+testsNeedTcltest 1.0
+
+testing {
+ useLocal dns.tcl dns
+}
+
+# -------------------------------------------------------------------------
+# Tests
+# -------------------------------------------------------------------------
+
+# Test the dns uri scheme split and join methods.
+
+set urls {
+ 1 dns:www.example.org
+ {class {} nameserver {} port {} query www.example.org scheme dns type {}}
+ 2 dns://nameserver/www.example.org
+ {class {} nameserver nameserver port {} query www.example.org scheme dns type {}}
+ 3 dns://nameserver:53/www.example.org
+ {class {} nameserver nameserver port 53 query www.example.org scheme dns type {}}
+ 4 dns:www.example.org?class=IN
+ {class IN nameserver {} port {} query www.example.org scheme dns type {}}
+ 5 dns:www.example.org?type=MX
+ {class {} nameserver {} port {} query www.example.org scheme dns type MX}
+ 6 dns:www.example.org?class=IN;type=A
+ {class IN nameserver {} port {} query www.example.org scheme dns type A}
+ 7 dns:www.example.org?type=A;class=IN
+ {class IN nameserver {} port {} query www.example.org scheme dns type A}
+}
+
+foreach {ndx url check} $urls {
+ test dns-1.$ndx [list uri::split $url] {
+ if {![catch {uri::split $url} result]} {
+ if {![catch {array set URL $result} result]} {
+ set result [dictsort [array get URL]]
+ }
+ }
+ set result
+ } $check
+}
+
+foreach {ndx url check} $urls {
+ if {$ndx == 6} continue; # this test is bogus for join.
+ test dns-2.$ndx [list uri::join $url] {
+ catch {eval [list uri::join] $check} result
+ set result
+ } $url
+}
+
+
+# -------------------------------------------------------------------------
+testsuiteCleanup
+
+# Local Variables:
+# mode: tcl
+# indent-tabs-mode: nil
+# End:
diff --git a/tcllib/modules/dns/ip.tcl b/tcllib/modules/dns/ip.tcl
new file mode 100644
index 0000000..f55ab3e
--- /dev/null
+++ b/tcllib/modules/dns/ip.tcl
@@ -0,0 +1,553 @@
+# ip.tcl - Copyright (C) 2004 Pat Thoyts <patthoyts@users.sourceforge.net>
+#
+# Internet address manipulation.
+#
+# RFC 3513: IPv6 addressing.
+#
+# -------------------------------------------------------------------------
+# See the file "license.terms" for information on usage and redistribution
+# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+# -------------------------------------------------------------------------
+
+# @mdgen EXCLUDE: ipMoreC.tcl
+
+package require Tcl 8.2; # tcl minimum version
+
+namespace eval ip {
+ namespace export is version normalize equal type contract mask collapse subtract
+ #catch {namespace ensemble create}
+
+ variable IPv4Ranges
+ if {![info exists IPv4Ranges]} {
+ array set IPv4Ranges {
+ 0/8 private
+ 10/8 private
+ 127/8 private
+ 172.16/12 private
+ 192.168/16 private
+ 223/8 reserved
+ 224/3 reserved
+ }
+ }
+
+ variable IPv6Ranges
+ if {![info exists IPv6Ranges]} {
+ # RFC 3513: 2.4
+ # RFC 3056: 2
+ array set IPv6Ranges {
+ 2002::/16 "6to4 unicast"
+ fe80::/10 "link local"
+ fec0::/10 "site local"
+ ff00::/8 "multicast"
+ ::/128 "unspecified"
+ ::1/128 "localhost"
+ }
+ }
+}
+
+proc ::ip::is {class ip} {
+ foreach {ip mask} [split $ip /] break
+ switch -exact -- $class {
+ ipv4 - IPv4 - 4 {
+ return [IPv4? $ip]
+ }
+ ipv6 - IPv6 - 6 {
+ return [IPv6? $ip]
+ }
+ default {
+ return -code error "bad class \"$class\": must be ipv4 or ipv6"
+ }
+ }
+}
+
+proc ::ip::version {ip} {
+ set version -1
+ if {[string equal $ip {}]} { return $version}
+ foreach {addr mask} [split $ip /] break
+ if {[IPv4? $addr]} {
+ set version 4
+ } elseif {[IPv6? $addr]} {
+ set version 6
+ }
+ return $version
+}
+
+proc ::ip::equal {lhs rhs} {
+ foreach {LHS LM} [SplitIp $lhs] break
+ foreach {RHS RM} [SplitIp $rhs] break
+ if {[set version [version $LHS]] != [version $RHS]} {
+ return -code error "type mismatch:\
+ cannot compare different address types"
+ }
+ if {$version == 4} {set fmt I} else {set fmt I4}
+ set LHS [Mask$version [Normalize $LHS $version] $LM]
+ set RHS [Mask$version [Normalize $RHS $version] $RM]
+ binary scan $LHS $fmt LLL
+ binary scan $RHS $fmt RRR
+ foreach L $LLL R $RRR {
+ if {$L != $R} {return 0}
+ }
+ return 1
+}
+
+proc ::ip::collapse {prefixlist} {
+ #puts **[llength $prefixlist]||$prefixlist
+
+ # Force mask parts into length notation for the following merge
+ # loop to work.
+ foreach ip $prefixlist {
+ foreach {addr mask} [SplitIp $ip] break
+ set nip $addr/[maskToLength [maskToInt $mask]]
+ #puts "prefix $ip = $nip"
+ lappend tmp $nip
+ }
+ set prefixlist $tmp
+
+ #puts @@[llength $prefixlist]||$prefixlist
+
+ set ret {}
+ set can_normalize_more 1
+ while {$can_normalize_more} {
+ set prefixlist [lsort -dict $prefixlist]
+
+ #puts ||[llength $prefixlist]||$prefixlist
+
+ set can_normalize_more 0
+
+ for {set idx 0} {$idx < [llength $prefixlist]} {incr idx} {
+ set nextidx [expr {$idx + 1}]
+
+ set item [lindex $prefixlist $idx]
+ set nextitem [lindex $prefixlist $nextidx]
+
+ if {$nextitem eq ""} {
+ lappend ret $item
+ continue
+ }
+
+ set itemmask [mask $item]
+ set nextitemmask [mask $nextitem]
+
+ set item [prefix $item]
+
+ if {$itemmask ne $nextitemmask} {
+ lappend ret $item/$itemmask
+ continue
+ }
+
+ set adjacentitem [intToString [nextNet $item $itemmask]]/$itemmask
+
+ if {$nextitem ne $adjacentitem} {
+ lappend ret $item/$itemmask
+ continue
+ }
+
+ set upmask [expr {$itemmask - 1}]
+ set upitem "$item/$upmask"
+
+ # Maybe just checking the llength of the result is enough ?
+ if {[reduceToAggregates [list $item $nextitem $upitem]] != [list $upitem]} {
+ lappend ret $item/$itemmask
+ continue
+ }
+
+ set can_normalize_more 1
+
+ incr idx
+ lappend ret $upitem
+ }
+
+ set prefixlist $ret
+ set ret {}
+ }
+
+ return $prefixlist
+}
+
+
+proc ::ip::normalize {ip {Ip4inIp6 0}} {
+ foreach {ip mask} [SplitIp $ip] break
+ set version [version $ip]
+ set s [ToString [Normalize $ip $version] $Ip4inIp6]
+ if {($version == 6 && $mask != 128) || ($version == 4 && $mask != 32)} {
+ append s /$mask
+ }
+ return $s
+}
+
+proc ::ip::contract {ip} {
+ foreach {ip mask} [SplitIp $ip] break
+ set version [version $ip]
+ set s [ToString [Normalize $ip $version]]
+ if {$version == 6} {
+ set r ""
+ foreach o [split $s :] {
+ append r [format %x: 0x$o]
+ }
+ set r [string trimright $r :]
+ regsub {(?:^|:)0(?::0)+(?::|$)} $r {::} r
+ } else {
+ set r [string trimright $s .0]
+ }
+ return $r
+}
+
+proc ::ip::subtract {hosts} {
+ set positives {}
+ set negatives {}
+
+ foreach host $hosts {
+ foreach {addr mask} [SplitIp $host] break
+ set host $addr/[maskToLength [maskToInt $mask]]
+
+ if {[string match "-*" $host]} {
+ set host [string trimleft $host "-"]
+ lappend negatives $host
+ } else {
+ lappend positives $host
+ }
+ }
+
+ # Reduce to aggregates if needed
+ if {[llength $positives] > 1} {
+ set positives [reduceToAggregates $positives]
+ }
+
+ if {![llength $positives]} {
+ return {}
+ }
+
+ if {[llength $negatives] > 1} {
+ set negatives [reduceToAggregates $negatives]
+ }
+
+ if {![llength $negatives]} {
+ return $positives
+ }
+
+ # Remove positives that are cancelled out entirely
+ set new_positives {}
+ foreach positive $positives {
+ set found 0
+ foreach negative $negatives {
+ # Do we need the exact check, i.e. ==, or 'eq', or would
+ # checking the length of result == 1 be good enough?
+ if {[reduceToAggregates [list $positive $negative]] == [list $negative]} {
+ set found 1
+ break
+ }
+ }
+
+ if {!$found} {
+ lappend new_positives $positive
+ }
+ }
+ set positives $new_positives
+
+ set retval {}
+ foreach positive $positives {
+ set negatives_found {}
+ foreach negative $negatives {
+ if {[isOverlap $positive $negative]} {
+ lappend negatives_found $negative
+ }
+ }
+
+ if {![llength $negatives_found]} {
+ lappend retval $positive
+ continue
+ }
+
+ # Convert the larger subnet
+ ## Determine smallest subnet involved
+ set maxmask 0
+ foreach subnet [linsert $negatives 0 $positive] {
+ set mask [mask $subnet]
+ if {$mask > $maxmask} {
+ set maxmask $mask
+ }
+ }
+
+ set positive_list [ExpandSubnet $positive $maxmask]
+ set negative_list {}
+ foreach negative $negatives_found {
+ foreach negative_subnet [ExpandSubnet $negative $maxmask] {
+ lappend negative_list $negative_subnet
+ }
+ }
+
+ foreach positive_sub $positive_list {
+ if {[lsearch -exact $negative_list $positive_sub] < 0} {
+ lappend retval $positive_sub
+ }
+ }
+ }
+
+ return $retval
+}
+
+proc ::ip::ExpandSubnet {subnet newmask} {
+ #set oldmask [maskToLength [maskToInt [mask $subnet]]]
+ set oldmask [mask $subnet]
+ set subnet [prefix $subnet]
+
+ set numsubnets [expr {round(pow(2, ($newmask - $oldmask)))}]
+
+ set ret {}
+ for {set idx 0} {$idx < $numsubnets} {incr idx} {
+ lappend ret "${subnet}/${newmask}"
+ set subnet [intToString [nextNet $subnet $newmask]]
+ }
+
+ return $ret
+}
+
+# Returns an IP address prefix.
+# For instance:
+# prefix 192.168.1.4/16 => 192.168.0.0
+# prefix fec0::4/16 => fec0:0:0:0:0:0:0:0
+# prefix fec0::4/ffff:: => fec0:0:0:0:0:0:0:0
+#
+proc ::ip::prefix {ip} {
+ foreach {addr mask} [SplitIp $ip] break
+ set version [version $addr]
+ set addr [Normalize $addr $version]
+ return [ToString [Mask$version $addr $mask]]
+}
+
+# Return the address type. For IPv4 this is one of private, reserved
+# or normal
+# For IPv6 it is one of site local, link local, multicast, unicast,
+# unspecified or loopback.
+proc ::ip::type {ip} {
+ set version [version $ip]
+ upvar [namespace current]::IPv${version}Ranges types
+ set ip [prefix $ip]
+ foreach prefix [array names types] {
+ set mask [mask $prefix]
+ if {[equal $ip/$mask $prefix]} {
+ return $types($prefix)
+ }
+ }
+ if {$version == 4} {
+ return "normal"
+ } else {
+ return "unicast"
+ }
+}
+
+proc ::ip::mask {ip} {
+ foreach {addr mask} [split $ip /] break
+ return $mask
+}
+
+# -------------------------------------------------------------------------
+
+# Returns true is the argument can be converted into an IPv4 address.
+#
+proc ::ip::IPv4? {ip} {
+ if {[string first : $ip] >= 0} {
+ return 0
+ }
+ if {[catch {Normalize4 $ip}]} {
+ return 0
+ }
+ return 1
+}
+
+proc ::ip::IPv6? {ip} {
+ set octets [split $ip :]
+ if {[llength $octets] < 3 || [llength $octets] > 8} {
+ return 0
+ }
+ set ndx 0
+ foreach octet $octets {
+ incr ndx
+ if {[string length $octet] < 1} continue
+ if {[regexp {^[a-fA-F\d]{1,4}$} $octet]} continue
+ if {$ndx >= [llength $octets] && [IPv4? $octet]} continue
+ if {$ndx == 2 && [lindex $octets 0] == 2002 && [IPv4? $octet]} continue
+ #"Invalid IPv6 address \"$ip\""
+ return 0
+ }
+ if {[regexp {^:[^:]} $ip]} {
+ #"Invalid ipv6 address \"$ip\" (starts with :)"
+ return 0
+ }
+ if {[regexp {[^:]:$} $ip]} {
+ # "Invalid IPv6 address \"$ip\" (ends with :)"
+ return 0
+ }
+ if {[regsub -all :: $ip "|" junk] > 1} {
+ # "Invalid IPv6 address \"$ip\" (more than one :: pattern)"
+ return 0
+ }
+ return 1
+}
+
+proc ::ip::Mask4 {ip {bits {}}} {
+ if {[string length $bits] < 1} { set bits 32 }
+ binary scan $ip I ipx
+ if {[string is integer $bits]} {
+ set mask [expr {(0xFFFFFFFF << (32 - $bits)) & 0xFFFFFFFF}]
+ } else {
+ binary scan [Normalize4 $bits] I mask
+ }
+ return [binary format I [expr {$ipx & $mask}]]
+}
+
+proc ::ip::Mask6 {ip {bits {}}} {
+ if {[string length $bits] < 1} { set bits 128 }
+ if {[string is integer $bits]} {
+ set mask [binary format B128 [string repeat 1 $bits]]
+ } else {
+ binary scan [Normalize6 $bits] I4 mask
+ }
+ binary scan $ip I4 Addr
+ binary scan $mask I4 Mask
+ foreach A $Addr M $Mask {
+ lappend r [expr {$A & $M}]
+ }
+ return [binary format I4 $r]
+}
+
+
+
+# A network address specification is an IPv4 address with an optional bitmask
+# Split an address specification into a IPv4 address and a network bitmask.
+# This doesn't validate the address portion.
+# If a spec with no mask is provided then the mask will be 32
+# (all bits significant).
+# Masks may be either integer number of significant bits or dotted-quad
+# notation.
+#
+proc ::ip::SplitIp {spec} {
+ set slash [string last / $spec]
+ if {$slash != -1} {
+ incr slash -1
+ set ip [string range $spec 0 $slash]
+ incr slash 2
+ set bits [string range $spec $slash end]
+ } else {
+ set ip $spec
+ if {[string length $ip] > 0 && [version $ip] == 6} {
+ set bits 128
+ } else {
+ set bits 32
+ }
+ }
+ return [list $ip $bits]
+}
+
+# Given an IP string from the user, convert to a normalized internal rep.
+# For IPv4 this is currently a hex string (0xHHHHHHHH).
+# For IPv6 this is a binary string or 16 chars.
+proc ::ip::Normalize {ip {version 0}} {
+ if {$version < 0} {
+ set version [version $ip]
+ if {$version < 0} {
+ return -code error "invalid address \"$ip\":\
+ value must be a valid IPv4 or IPv6 address"
+ }
+ }
+ return [Normalize$version $ip]
+}
+
+proc ::ip::Normalize4 {ip} {
+ set octets [split $ip .]
+ if {[llength $octets] > 4} {
+ return -code error "invalid ip address \"$ip\""
+ } elseif {[llength $octets] < 4} {
+ set octets [lrange [concat $octets 0 0 0] 0 3]
+ }
+ foreach oct $octets {
+ if {$oct < 0 || $oct > 255} {
+ return -code error "invalid ip address"
+ }
+ }
+ return [binary format c4 $octets]
+}
+
+proc ::ip::Normalize6 {ip} {
+ set octets [split $ip :]
+ set ip4embed [string first . $ip]
+ set len [llength $octets]
+ if {$len < 0 || $len > 8} {
+ return -code error "invalid address: this is not an IPv6 address"
+ }
+ set result ""
+ for {set n 0} {$n < $len} {incr n} {
+ set octet [lindex $octets $n]
+ if {$octet == {}} {
+ if {$n == 0 || $n == ($len - 1)} {
+ set octet \0\0
+ } else {
+ set missing [expr {9 - $len}]
+ if {$ip4embed != -1} {incr missing -1}
+ set octet [string repeat \0\0 $missing]
+ }
+ } elseif {[string first . $octet] != -1} {
+ set octet [Normalize4 $octet]
+ } else {
+ set m [expr {4 - [string length $octet]}]
+ if {$m != 0} {
+ set octet [string repeat 0 $m]$octet
+ }
+ set octet [binary format H4 $octet]
+ }
+ append result $octet
+ }
+ if {[string length $result] != 16} {
+ return -code error "invalid address: \"$ip\" is not an IPv6 address"
+ }
+ return $result
+}
+
+
+# This will convert a full ipv4/ipv6 in binary format into a normal
+# expanded string rep.
+proc ::ip::ToString {bin {Ip4inIp6 0}} {
+ set len [string length $bin]
+ set r ""
+ if {$len == 4} {
+ binary scan $bin c4 octets
+ foreach octet $octets {
+ lappend r [expr {$octet & 0xff}]
+ }
+ return [join $r .]
+ } elseif {$len == 16} {
+ if {$Ip4inIp6 == 0} {
+ binary scan $bin H32 hex
+ for {set n 0} {$n < 32} {incr n} {
+ append r [string range $hex $n [incr n 3]]:
+ }
+ return [string trimright $r :]
+ } else {
+ binary scan $bin H24c4 hex octets
+ for {set n 0} {$n < 24} {incr n} {
+ append r [string range $hex $n [incr n 3]]:
+ }
+ foreach octet $octets {
+ append r [expr {$octet & 0xff}].
+ }
+ return [string trimright $r .]
+ }
+ } else {
+ return -code error "invalid binary address:\
+ argument is neither an IPv4 nor an IPv6 address"
+ }
+}
+
+# -------------------------------------------------------------------------
+# Load extended command set.
+
+source [file join [file dirname [info script]] ipMore.tcl]
+
+# -------------------------------------------------------------------------
+
+package provide ip 1.3
+
+# -------------------------------------------------------------------------
+# Local Variables:
+# indent-tabs-mode: nil
+# End:
diff --git a/tcllib/modules/dns/ip.test b/tcllib/modules/dns/ip.test
new file mode 100644
index 0000000..1affbc4
--- /dev/null
+++ b/tcllib/modules/dns/ip.test
@@ -0,0 +1,271 @@
+# ip.test - Copyright (C) 2002 Pat Thoyts <patthoyts@users.sourceforge.net>
+#
+# Tests for the Tcllib ip package
+#
+# -------------------------------------------------------------------------
+# See the file "license.terms" for information on usage and redistribution
+# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+# -------------------------------------------------------------------------
+# RCS: @(#) $Id: ip.test,v 1.9 2010/08/16 17:35:18 andreas_kupries Exp $
+
+# -------------------------------------------------------------------------
+
+source [file join \
+ [file dirname [file dirname [file join [pwd] [info script]]]] \
+ devtools testutilities.tcl]
+
+testsNeedTcl 8.2
+testsNeedTcltest 1.0
+
+testing {
+ useLocal ip.tcl ip
+}
+
+# -------------------------------------------------------------------------
+# Tests
+# -------------------------------------------------------------------------
+
+# version
+set Data {
+ 127.0.0.1 4
+ 0.0.0.0 4
+ 192.168.0.4 4
+ 255.255.255.255 4
+ 127/8 4
+ 192/16 4
+ :: 6
+ ::1 6
+ fec0::1 6
+ ::192.168.0.4 6
+ fec0:0:0:0:0:0:0:1 6
+ fffe:0:0::2 6
+ 2002:192.168.0.4:: 6
+ 2001:192.168.0.4:: -1
+ 2002:127.0.0.1::1 6
+ hello -1
+ -1 -1
+ 1.2.3.4.example.com -1
+ bogus.1.2.3.4.example.com -1
+ {} -1
+}
+set n 0
+foreach {addr result} $Data {
+ test ip-1.[incr n] [list ip version $addr] {
+ list [catch {ip::version $addr} msg] $addr $msg
+ } [list 0 $addr $result]
+}
+
+# is
+set n 0
+foreach {addr result} $Data {
+ if {$result != 4} {set result 0}
+ test ip-2.[incr n] [list ip::is ipv4 $addr] {
+ list [catch {expr {[ip::is ipv4 $addr] ? 4 : 0}} msg] $addr $msg
+ } [list 0 $addr $result]
+}
+
+set n 0
+foreach {addr result} $Data {
+ if {$result != 6} {set result 0}
+ test ip-3.[incr n] [list ip::is ipv6 $addr] {
+ list [catch {expr {[ip::is ipv6 $addr] ? 6 : 0}} msg] $addr $msg
+ } [list 0 $addr $result]
+}
+
+# normalize
+set Data {
+ 192.168.0.4/32 192.168.0.4
+ 192.168.0.4/24 192.168.0.4/24
+ 192.168 192.168.0.0
+ 192.168/24 192.168.0.0/24
+ 192.168/255.255.0.0 192.168.0.0/255.255.0.0
+ :: 0000:0000:0000:0000:0000:0000:0000:0000
+ ::1 0000:0000:0000:0000:0000:0000:0000:0001
+ fec0::1 fec0:0000:0000:0000:0000:0000:0000:0001
+ fec0:0:0::1 fec0:0000:0000:0000:0000:0000:0000:0001
+ fec0:0::8:0:1 fec0:0000:0000:0000:0000:0008:0000:0001
+ ::192.168.0.4 0000:0000:0000:0000:0000:0000:c0a8:0004
+ ::ffff:192.168.0.4 0000:0000:0000:0000:0000:ffff:c0a8:0004
+ fec0::1/16 fec0:0000:0000:0000:0000:0000:0000:0001/16
+ fec0::1/128 fec0:0000:0000:0000:0000:0000:0000:0001
+ 2002:127.0.0.1::1 2002:7f00:0001:0000:0000:0000:0000:0001
+}
+set n 0
+foreach {addr result} $Data {
+ test ip-4.[incr n] [list ip::normalize $addr] {
+ list [catch {ip::normalize $addr} msg] $msg
+ } [list 0 $result]
+}
+
+set Data {
+ 192.168.1.4 8 192.0.0.0
+ 192.168.1.4 1 128.0.0.0
+ 192.168.1.4 16 192.168.0.0
+ 192.169.1.4 15 192.168.0.0
+ 192.168.1.4 24 192.168.1.0
+ 192.168.1.4 32 192.168.1.4
+ fec0:fafa::1 64 fec0:fafa:0000:0000:0000:0000:0000:0000
+ fec0:fafa::1 8 fe00:0000:0000:0000:0000:0000:0000:0000
+ fec0:fafa::1 10 fec0:0000:0000:0000:0000:0000:0000:0000
+ fec0:fafa::1 128 fec0:fafa:0000:0000:0000:0000:0000:0001
+}
+
+# prefix
+set n 0
+foreach {addr mask prefix} $Data {
+ test ip-5.[incr n] [list ip::prefix $addr/$mask] {
+ list [catch {ip::prefix $addr/$mask} msg] $msg
+ } [list 0 $prefix]
+}
+
+# mask
+
+set n 0
+foreach {addr mask prefix} $Data {
+ test ip-6.[incr n] [list ip::mask $addr/$mask] {
+ list [catch {ip::mask $addr/$mask} msg] $msg
+ } [list 0 $mask]
+}
+
+# equal
+set Data {
+ 192.168.0.4 ::1 1 "type mismatch: cannot compare different address types"
+ 192.168.1.4/16 192.168.0.0/16 0 1
+ 192.169.1.4/16 192.168.0.0/16 0 0
+ 192.169.1.4/15 192.168.0.0/15 0 1
+ 192.168.1.4/24 192.168.1.0/24 0 1
+ 127/8 192/8 0 0
+ 192.168.1.4/255.255.0.0 192.168.1.4/16 0 1
+ 192.169.1.4/255.255.0.0 192.168.1.4/16 0 0
+ 192.169.1.4/255.254.0.0 192.168.1.4/16 0 1
+
+ fec0::1/10 fec0::2/10 0 1
+ ::1/64 ::2/64 0 1
+ ::1/128 ::2/128 0 0
+ ::1/127 ::2/127 0 0
+ ::1/126 ::2/126 0 1
+ fec0:ffff::1/16 fec0:aaaa::2/16 0 1
+ fec0:ffff::1/17 fec0:aaaa::2/17 0 1
+ fec0:ffff::1/18 fec0:aaaa::2/18 0 0
+}
+set n 0
+foreach {lhs rhs err result} $Data {
+ test ip-7.[incr n] [list ip::equal $lhs $rhs] {
+ list [catch {ip::equal $lhs $rhs} msg] $msg
+ } [list $err $result]
+}
+
+# contract
+set Data {
+ 127.0.0.1 127.0.0.1
+ 127.0.0.0 127
+ 0000:0000:0000:0000:0000:0000:0000:0000 ::
+ 0000:0000:0000:0000:0000:0000:0000:0001 ::1
+ fec0:0000:0000:0000:0000:0000:0000:0000 fec0::
+ fec0:0000:0000:0000:0000:0000:0000:0001 fec0::1
+ fec0:0000:0001:0000:0000:0000:0000:0001 fec0:0:1::1
+ fec0:0001:0002:0003:0004:0005:0006:0001 fec0:1:2:3:4:5:6:1
+ fec0:0001:2002:0003:0004:0005:0006:0001 fec0:1:2002:3:4:5:6:1
+}
+set n 0
+foreach {addr result} $Data {
+ test ip-8.[incr n] [list ip::contract $addr] {
+ list [catch {ip::contract $addr} msg] $msg
+ } [list 0 $result]
+}
+
+# -------------------------------------------------------------------------
+
+test ip-9.0 {collapse} {
+ ip::collapse {1.2.2.0/24 1.2.3.0/24}
+} 1.2.2.0/23
+
+test ip-9.1 {collapse revers} {
+ ip::collapse {1.2.3.0/24 1.2.2.0/24}
+} 1.2.2.0/23
+
+test ip-9.2 {collapse} {
+ set l {}
+ for {set n 0} {$n < 256} {incr n} {
+ lappend l 1.2.$n.0/24
+ }
+ ip::collapse $l
+} {1.2.0.0/16}
+
+test ip-9.3 {collapse revers} {
+ set l {}
+ for {set n 255} {$n >= 0} {incr n -1} {
+ lappend l 1.2.$n.0/24
+ }
+ ip::collapse $l
+} {1.2.0.0/16}
+
+test ip-9.4 {collapse} {
+ ip::collapse {1.2.2.0/255.255.255.0 1.2.3.0/255.255.255.0}
+} 1.2.2.0/23
+
+test ip-9.5 {collapse revers} {
+ ip::collapse {1.2.3.0/255.255.255.0 1.2.2.0/255.255.255.0}
+} 1.2.2.0/23
+
+# -------------------------------------------------------------------------
+
+test ip-10.0 {subtract} {
+ ip::collapse [ip::subtract {1.2.0.0/16 1.3.0.0/16 -1.2.4.0/24}]
+} {1.2.0.0/22 1.2.5.0/24 1.2.6.0/23 1.2.8.0/21 1.2.16.0/20 1.2.32.0/19 1.2.64.0/18 1.2.128.0/17 1.3.0.0/16}
+
+# -------------------------------------------------------------------------
+
+foreach {i m e} {
+ 0 255.255.255.255 32 5 0xffffffff 32
+ 1 255.255.255.0 24 6 0xffffff00 24
+ 2 255.255.0.0 16 7 0xffff0000 16
+ 3 255.0.0.0 8 8 0xff000000 8
+ 4 0.0.0.0 0 9 0x00000000 0
+} {
+ test ip-11.$i "maskToLength, $m" {
+ ip::maskToLength $m
+ } $e
+}
+
+# -------------------------------------------------------------------------
+
+foreach {i ip e} {
+ 0 0.0.0.0 0
+ 1 0.0.0.1 1
+ 2 0.0.1.0 256
+ 3 0.1.0.0 65536
+ 4 1.0.0.0 16777216
+ 5 0.0.0.255 255
+ 6 0.0.255.0 65280
+ 7 0.255.0.0 16711680
+ 8 255.0.0.0 4278190080
+ 9 255.255.255.255 4294967295
+} {
+ test ip-12.$i "toInteger $ip" {
+ ip::toInteger $ip
+ } $e
+}
+
+# -------------------------------------------------------------------------
+
+foreach {i pma e} {
+ 0 {1.1.1.1 24} 1.1.2.1/0
+ 1 {1.1.1.1 24 0} 1.1.1.1/0
+ 2 {1.1.1.1 24 1} 1.1.2.1/0
+ 3 {1.1.1.1 24 2} 1.1.3.1/0
+} {
+ test ip-13.$i "nextNet $pma" {
+ ip::nativeToPrefix [eval ip::nextNet $pma]
+ } $e
+}
+
+
+# -------------------------------------------------------------------------
+
+testsuiteCleanup
+
+# Local Variables:
+# mode: tcl
+# indent-tabs-mode: nil
+# End:
diff --git a/tcllib/modules/dns/ipMore.tcl b/tcllib/modules/dns/ipMore.tcl
new file mode 100644
index 0000000..942f64c
--- /dev/null
+++ b/tcllib/modules/dns/ipMore.tcl
@@ -0,0 +1,1295 @@
+#temporary home until this gets cleaned up for export to tcllib ip module
+# $Id: ipMore.tcl,v 1.4 2006/01/22 00:27:22 andreas_kupries Exp $
+
+
+##Library Header
+#
+# Copyright (c) 2005 Cisco Systems, Inc.
+#
+# Name:
+# ipMore
+#
+# Purpose:
+# Additional commands for the tcllib ip package.
+#
+# Author:
+# Aamer Akhter / aakhter@cisco.com
+#
+# Support Alias:
+# aakhter@cisco.com
+#
+# Usage:
+# package require ip
+# (The command are loaded from the regular package).
+#
+# Description:
+# A detailed description of the functionality provided by the library.
+#
+# Requirements:
+#
+# Variables:
+# namespace ::ip
+#
+# Notes:
+# 1.
+#
+# Keywords:
+#
+#
+# Category:
+#
+#
+# End of Header
+
+package require msgcat
+
+# Try to load various C based accelerator packages for two of the
+# commands.
+
+if {[catch {package require ipMorec}]} {
+ catch {package require tcllibc}
+}
+
+if {[llength [info commands ::ip::prefixToNativec]]} {
+ # An accelerator is present, providing the C variants
+ interp alias {} ::ip::prefixToNative {} ::ip::prefixToNativec
+ interp alias {} ::ip::isOverlapNative {} ::ip::isOverlapNativec
+} else {
+ # Link API to the Tcl variants, no accelerators are available.
+ interp alias {} ::ip::prefixToNative {} ::ip::prefixToNativeTcl
+ interp alias {} ::ip::isOverlapNative {} ::ip::isOverlapNativeTcl
+}
+
+namespace eval ::ip {
+ ::msgcat::mcload [file join [file dirname [info script]] msgs]
+}
+
+if {![llength [info commands lassign]]} {
+ # Either an older tcl version, or tclx not loaded; have to use our
+ # internal lassign from http://wiki.tcl.tk/1530 by Schelte Bron
+
+ proc ::ip::lassign {values args} {
+ uplevel 1 [list foreach $args $values break]
+ lrange $values [llength $args] end
+ }
+}
+if {![llength [info commands lvarpop]]} {
+ # Define an emulation of Tclx's lvarpop if the command
+ # is not present already.
+
+ proc ::ip::lvarpop {upVar {index 0}} {
+ upvar $upVar list;
+ set top [lindex $list $index];
+ set list [concat [lrange $list 0 [expr $index - 1]] \
+ [lrange $list [expr $index +1] end]];
+ return $top;
+ }
+}
+
+# Some additional aliases for backward compatability. Not
+# documented. The old names are from previous versions while at Cisco.
+#
+# Old command name --> Documented command name
+interp alias {} ::ip::ToInteger {} ::ip::toInteger
+interp alias {} ::ip::ToHex {} ::ip::toHex
+interp alias {} ::ip::MaskToInt {} ::ip::maskToInt
+interp alias {} ::ip::MaskToLength {} ::ip::maskToLength
+interp alias {} ::ip::LengthToMask {} ::ip::lengthToMask
+interp alias {} ::ip::IpToLayer2Multicast {} ::ip::ipToLayer2Multicast
+interp alias {} ::ip::IpHostFromPrefix {} ::ip::ipHostFromPrefix
+
+
+##Procedure Header
+# Copyright (c) 2004 Cisco Systems, Inc.
+#
+# Name:
+# ::ip::prefixToNative
+#
+# Purpose:
+# convert from dotted from to native (hex) form
+#
+# Synopsis:
+# prefixToNative <prefix>
+#
+# Arguments:
+# <prefix>
+# string in the <ipaddr>/<mask> format
+#
+# Return Values:
+# <prefix> in native format {<hexip> <hexmask>}
+#
+# Description:
+#
+# Examples:
+# % ip::prefixToNative 1.1.1.0/24
+# 0x01010100 0xffffff00
+#
+# Sample Input:
+#
+# Sample Output:
+# Notes:
+# fixed bug in C extension that modified
+# calling context variable
+# See Also:
+#
+# End of Header
+
+proc ip::prefixToNativeTcl {prefix} {
+ set plist {}
+ foreach p $prefix {
+ set newPrefix [ip::toHex [ip::prefix $p]]
+ if {[string equal [set mask [ip::mask $p]] ""]} {
+ set newMask 0xffffffff
+ } else {
+ set newMask [format "0x%08x" [ip::maskToInt $mask]]
+ }
+ lappend plist [list $newPrefix $newMask]
+ }
+ if {[llength $plist]==1} {return [lindex $plist 0]}
+ return $plist
+}
+
+##Procedure Header
+# Copyright (c) 2004 Cisco Systems, Inc.
+#
+# Name:
+# ::ip::nativeToPrefix
+#
+# Purpose:
+# convert from native (hex) form to dotted form
+#
+# Synopsis:
+# nativeToPrefix <nativeList>|<native> [-ipv4]
+#
+# Arguments:
+# <nativeList>
+# list of native form ip addresses native form is:
+# <native>
+# tcllist in format {<hexip> <hexmask>}
+# -ipv4
+# the provided native format addresses are in ipv4 format (default)
+#
+# Return Values:
+# if nativeToPrefix is called with <native> a single (non-listified) address
+# is returned
+# if nativeToPrefix is called with a <nativeList> address list, then
+# a list of addresses is returned
+#
+# return form is: <ipaddr>/<mask>
+#
+# Description:
+#
+# Examples:
+# % ip::nativeToPrefix {0x01010100 0xffffff00} -ipv4
+# 1.1.1.0/24
+#
+# Sample Input:
+#
+# Sample Output:
+
+# Notes:
+#
+# See Also:
+#
+# End of Header
+
+proc ::ip::nativeToPrefix {nativeList args} {
+ set pList 1
+ set ipv4 1
+ while {[llength $args]} {
+ switch -- [lindex $args 0] {
+ -ipv4 {set args [lrange $args 1 end]}
+ default {
+ return -code error [msgcat::mc "option %s not supported" [lindex $args 0]]
+ }
+ }
+ }
+
+ # if a single native element is passed eg {0x01010100 0xffffff00}
+ # instead of {{0x01010100 0xffffff00} {0x01010100 0xffffff00}...}
+ # then return a (non-list) single entry
+ if {[llength [lindex $nativeList 0]]==1} {set pList 0; set nativeList [list $nativeList]}
+ foreach native $nativeList {
+ lassign $native ip mask
+ if {[string equal $mask ""]} {set mask 32}
+ set pString ""
+ append pString [ip::ToString [binary format I [expr {$ip}]]]
+ append pString "/"
+ append pString [ip::maskToLength $mask]
+ lappend rList $pString
+ }
+ # a multi (listified) entry was given
+ # return the listified entry
+ if {$pList} { return $rList }
+ return $pString
+}
+
+##Procedure Header
+# Copyright (c) 2004 Cisco Systems, Inc.
+#
+# Name:
+# ::ip::intToString
+#
+# Purpose:
+# convert from an integer/hex to dotted form
+#
+# Synopsis:
+# intToString <integer/hex> [-ipv4]
+#
+# Arguments:
+# <integer>
+# ip address in integer form
+# -ipv4
+# the provided integer addresses is ipv4 (default)
+#
+# Return Values:
+# ip address in dotted form
+#
+# Description:
+#
+# Examples:
+# ip::intToString 4294967295
+# 255.255.255.255
+#
+# Sample Input:
+#
+# Sample Output:
+
+# Notes:
+#
+# See Also:
+#
+# End of Header
+
+proc ::ip::intToString {int args} {
+ set ipv4 1
+ while {[llength $args]} {
+ switch -- [lindex $args 0] {
+ -ipv4 {set args [lrange $args 1 end]}
+ default {
+ return -code error [msgcat::mc "option %s not supported" [lindex $args 0]]
+ }
+ }
+ }
+ return [ip::ToString [binary format I [expr {$int}]]]
+}
+
+
+##Procedure Header
+# Copyright (c) 2004 Cisco Systems, Inc.
+#
+# Name:
+# ::ip::toInteger
+#
+# Purpose:
+# convert dotted form ip to integer
+#
+# Synopsis:
+# toInteger <ipaddr>
+#
+# Arguments:
+# <ipaddr>
+# decimal dotted form ip address
+#
+# Return Values:
+# integer form of <ipaddr>
+#
+# Description:
+#
+# Examples:
+# % ::ip::toInteger 1.1.1.0
+# 16843008
+#
+# Sample Input:
+#
+# Sample Output:
+
+# Notes:
+#
+# See Also:
+#
+# End of Header
+
+proc ::ip::toInteger {ip} {
+ binary scan [ip::Normalize4 $ip] I out
+ return [format %lu [expr {$out & 0xffffffff}]]
+}
+
+##Procedure Header
+# Copyright (c) 2004 Cisco Systems, Inc.
+#
+# Name:
+# ::ip::toHex
+#
+# Purpose:
+# convert dotted form ip to hex
+#
+# Synopsis:
+# toHex <ipaddr>
+#
+# Arguments:
+# <ipaddr>
+# decimal dotted from ip address
+#
+# Return Values:
+# hex form of <ipaddr>
+#
+# Description:
+#
+# Examples:
+# % ::ip::toHex 1.1.1.0
+# 0x01010100
+#
+# Sample Input:
+#
+# Sample Output:
+
+# Notes:
+#
+# See Also:
+#
+# End of Header
+
+proc ::ip::toHex {ip} {
+ binary scan [ip::Normalize4 $ip] H8 out
+ return "0x$out"
+}
+
+##Procedure Header
+# Copyright (c) 2004 Cisco Systems, Inc.
+#
+# Name:
+# ::ip::maskToInt
+#
+# Purpose:
+# convert mask to integer
+#
+# Synopsis:
+# maskToInt <mask>
+#
+# Arguments:
+# <mask>
+# mask in either dotted form or mask length form (255.255.255.0 or 24)
+#
+# Return Values:
+# integer form of mask
+#
+# Description:
+#
+# Examples:
+# ::ip::maskToInt 24
+# 4294967040
+#
+#
+# Sample Input:
+#
+# Sample Output:
+
+# Notes:
+#
+# See Also:
+#
+# End of Header
+
+proc ::ip::maskToInt {mask} {
+ if {[string is integer -strict $mask]} {
+ set maskInt [expr {(0xFFFFFFFF << (32 - $mask))}]
+ } else {
+ binary scan [Normalize4 $mask] I maskInt
+ }
+ set maskInt [expr {$maskInt & 0xFFFFFFFF}]
+ return [format %u $maskInt]
+}
+
+##Procedure Header
+# Copyright (c) 2004 Cisco Systems, Inc.
+#
+# Name:
+# ::ip::broadcastAddress
+#
+# Purpose:
+# return broadcast address given prefix
+#
+# Synopsis:
+# broadcastAddress <prefix> [-ipv4]
+#
+# Arguments:
+# <prefix>
+# route in the form of <ipaddr>/<mask> or native form {<hexip> <hexmask>}
+# -ipv4
+# the provided native format addresses are in ipv4 format (default)
+# note: broadcast addresses are not valid in ipv6
+#
+#
+# Return Values:
+# ipaddress of broadcast
+#
+# Description:
+#
+# Examples:
+# ::ip::broadcastAddress 1.1.1.0/24
+# 1.1.1.255
+#
+# ::ip::broadcastAddress {0x01010100 0xffffff00}
+# 0x010101ff
+#
+# Sample Input:
+#
+# Sample Output:
+
+# Notes:
+#
+# See Also:
+#
+# End of Header
+
+proc ::ip::broadcastAddress {prefix args} {
+ set ipv4 1
+ while {[llength $args]} {
+ switch -- [lindex $args 0] {
+ -ipv4 {set args [lrange $args 1 end]}
+ default {
+ return -code error [msgcat::mc "option %s not supported" [lindex $args 0]]
+ }
+ }
+ }
+ if {[llength $prefix] == 2} {
+ lassign $prefix net mask
+ } else {
+ set net [maskToInt [ip::prefix $prefix]]
+ set mask [maskToInt [ip::mask $prefix]]
+ }
+ set ba [expr {$net | ((~$mask)&0xffffffff)}]
+
+ if {[llength $prefix]==2} {
+ return [format "0x%08x" $ba]
+ }
+ return [ToString [binary format I $ba]]
+}
+
+##Procedure Header
+# Copyright (c) 2004 Cisco Systems, Inc.
+#
+# Name:
+# ::ip::maskToLength
+#
+# Purpose:
+# converts dotted or integer form of mask to length
+#
+# Synopsis:
+# maskToLength <dottedMask>|<integerMask>|<hexMask> [-ipv4]
+#
+# Arguments:
+# <dottedMask>
+# <integerMask>
+# <hexMask>
+# mask to convert to prefix length format (eg /24)
+# -ipv4
+# the provided integer/hex format masks are ipv4 (default)
+#
+# Return Values:
+# prefix length
+#
+# Description:
+#
+# Examples:
+# ::ip::maskToLength 0xffffff00 -ipv4
+# 24
+#
+# % ::ip::maskToLength 255.255.255.0
+# 24
+#
+# Sample Input:
+#
+# Sample Output:
+# Notes:
+#
+# See Also:
+#
+# End of Header
+
+proc ::ip::maskToLength {mask args} {
+ set ipv4 1
+ while {[llength $args]} {
+ switch -- [lindex $args 0] {
+ -ipv4 {set args [lrange $args 1 end]}
+ default {
+ return -code error [msgcat::mc "option %s not supported" [lindex $args 0]]
+ }
+ }
+ }
+ #pick the fastest method for either format
+ if {[string is integer -strict $mask]} {
+ binary scan [binary format I [expr {$mask}]] B32 maskB
+ if {[regexp -all {^1+} $maskB ones]} {
+ return [string length $ones]
+ } else {
+ return 0
+ }
+ } else {
+ regexp {\/(.+)} $mask dumb mask
+ set prefix 0
+ foreach ipByte [split $mask {.}] {
+ switch $ipByte {
+ 255 {incr prefix 8; continue}
+ 254 {incr prefix 7}
+ 252 {incr prefix 6}
+ 248 {incr prefix 5}
+ 240 {incr prefix 4}
+ 224 {incr prefix 3}
+ 192 {incr prefix 2}
+ 128 {incr prefix 1}
+ 0 {}
+ default {
+ return -code error [msgcat::mc "not an ip mask: %s" $mask]
+ }
+ }
+ break
+ }
+ return $prefix
+ }
+}
+
+
+##Procedure Header
+# Copyright (c) 2004 Cisco Systems, Inc.
+#
+# Name:
+# ::ip::lengthToMask
+#
+# Purpose:
+# converts mask length to dotted mask form
+#
+# Synopsis:
+# lengthToMask <maskLength> [-ipv4]
+#
+# Arguments:
+# <maskLength>
+# mask length
+# -ipv4
+# the provided mask length is ipv4 (default)
+#
+# Return Values:
+# mask in dotted form
+#
+# Description:
+#
+# Examples:
+# ::ip::lengthToMask 24
+# 255.255.255.0
+#
+# Sample Input:
+#
+# Sample Output:
+# Notes:
+#
+# See Also:
+#
+# End of Header
+
+proc ::ip::lengthToMask {masklen args} {
+ while {[llength $args]} {
+ switch -- [lindex $args 0] {
+ -ipv4 {set args [lrange $args 1 end]}
+ default {
+ return -code error [msgcat::mc "option %s not supported" [lindex $args 0]]
+ }
+ }
+ }
+ # the fastest method is just to look
+ # thru an array
+ return $::ip::maskLenToDotted($masklen)
+}
+
+##Procedure Header
+# Copyright (c) 2004 Cisco Systems, Inc.
+#
+# Name:
+# ::ip::nextNet
+#
+# Purpose:
+# returns next an ipaddress in same position in next network
+#
+# Synopsis:
+# nextNet <ipaddr> <mask> [<count>] [-ipv4]
+#
+# Arguments:
+# <ipaddress>
+# in hex/integer/dotted format
+# <mask>
+# mask in hex/integer/dotted/maskLen format
+# <count>
+# number of nets to skip over (default is 1)
+# -ipv4
+# the provided hex/integer addresses are in ipv4 format (default)
+#
+# Return Values:
+# ipaddress in same position in next network in hex
+#
+# Description:
+#
+# Examples:
+#
+# Sample Input:
+#
+# Sample Output:
+# Notes:
+#
+# See Also:
+#
+# End of Header
+
+proc ::ip::nextNet {prefix mask args} {
+ set count 1
+ while {[llength $args]} {
+ switch -- [lindex $args 0] {
+ -ipv4 {set args [lrange $args 1 end]}
+ default {
+ set count [lindex $args 0]
+ set args [lrange $args 1 end]
+ }
+ }
+ }
+ if {![string is integer -strict $prefix]} {
+ set prefix [toInteger $prefix]
+ }
+ if {![string is integer -strict $mask] || ($mask < 33 && $mask > 0)} {
+ set mask [maskToInt $mask]
+ }
+ set prefix [expr {$prefix + ((($mask ^ 0xFFffFFff) + 1) * $count) }]
+ return [format "0x%08x" $prefix]
+}
+
+
+##Procedure Header
+# Copyright (c) 2004 Cisco Systems, Inc.
+#
+# Name:
+# ::ip::isOverlap
+#
+# Purpose:
+# checks to see if prefixes overlap
+#
+# Synopsis:
+# isOverlap <prefix> <prefix1> <prefix2>...
+#
+# Arguments:
+# <prefix>
+# in form <ipaddr>/<mask> prefix to compare <prefixN> against
+# <prefixN>
+# in form <ipaddr>/<mask> prefixes to compare against
+#
+# Return Values:
+# 1 if there is an overlap
+#
+# Description:
+#
+# Examples:
+# % ::ip::isOverlap 1.1.1.0/24 2.1.0.1/32
+# 0
+#
+# ::ip::isOverlap 1.1.1.0/24 2.1.0.1/32 1.1.1.1/32
+# 1
+# Sample Input:
+#
+# Sample Output:
+# Notes:
+#
+# See Also:
+#
+# End of Header
+
+proc ::ip::isOverlap {ip args} {
+ lassign [SplitIp $ip] ip1 mask1
+ set ip1int [toInteger $ip1]
+ set mask1int [maskToInt $mask1]
+
+ set overLap 0
+ foreach prefix $args {
+ lassign [SplitIp $prefix] ip2 mask2
+ set ip2int [toInteger $ip2]
+ set mask2int [maskToInt $mask2]
+ set mask1mask2 [expr {$mask1int & $mask2int}]
+ if {[expr {$ip1int & $mask1mask2}] == [expr {$ip2int & $mask1mask2}]} {
+ set overLap 1
+ break
+ }
+ }
+ return $overLap
+}
+
+
+#optimized overlap, that accepts native format
+
+##Procedure Header
+# Copyright (c) 2004 Cisco Systems, Inc.
+#
+# Name:
+# ::ip::isOverlapNative
+#
+# Purpose:
+# checks to see if prefixes overlap (optimized native form)
+#
+# Synopsis:
+# isOverlap <hexipaddr> <hexmask> {{<hexipaddr1> <hexmask1>} {<hexipaddr2> <hexmask2>...}
+#
+# Arguments:
+# -all
+# return all overlaps rather than the first one
+# -inline
+# rather than returning index values, return the actual overlap prefixes
+# <hexipaddr>
+# ipaddress in hex/integer form
+# <hexMask>
+# mask in hex/integer form
+# -ipv4
+# the provided native format addresses are in ipv4 format (default)
+#
+# Return Values:
+# non-zero if there is an overlap, value is element # in list with overlap
+#
+# Description:
+# isOverlapNative is available both as a C extension and in a native tcl form
+# if the extension is loaded (tried automatically), isOverlapNative will be
+# linked to isOverlapNativeC. If an extension is not loaded, then isOverlapNative
+# will be linked to the native tcl proc: ipOverlapNativeTcl.
+#
+# Examples:
+# % ::ip::isOverlapNative 0x01010100 0xffffff00 {{0x02010001 0xffffffff}}
+# 0
+#
+# %::ip::isOverlapNative 0x01010100 0xffffff00 {{0x02010001 0xffffffff} {0x01010101 0xffffffff}}
+# 2
+#
+# Sample Input:
+#
+# Sample Output:
+# Notes:
+#
+# See Also:
+#
+# End of Header
+
+proc ::ip::isOverlapNativeTcl {args} {
+ set all 0
+ set inline 0
+ set notOverlap 0
+ set ipv4 1
+ foreach sw [lrange $args 0 end-3] {
+ switch -exact -- $sw {
+ -all {
+ set all 1
+ set allList [list]
+ }
+ -inline {set inline 1}
+ -ipv4 {}
+ }
+ }
+ set args [lassign [lrange $args end-2 end] ip1int mask1int prefixList]
+ if {$inline} {
+ set overLap [list]
+ } else {
+ set overLap 0
+ }
+ set count 0
+ foreach prefix $prefixList {
+ incr count
+ lassign $prefix ip2int mask2int
+ set mask1mask2 [expr {$mask1int & $mask2int}]
+ if {[expr {$ip1int & $mask1mask2}] == [expr {$ip2int & $mask1mask2}]} {
+ if {$inline} {
+ set overLap [list $prefix]
+ } else {
+ set overLap $count
+ }
+ if {$all} {
+ if {$inline} {
+ lappend allList $prefix
+ } else {
+ lappend allList $count
+ }
+ } else {
+ break
+ }
+ }
+ }
+ if {$all} {return $allList}
+ return $overLap
+}
+
+##Procedure Header
+# Copyright (c) 2004 Cisco Systems, Inc.
+#
+# Name:
+# ::ip::ipToLayer2Multicast
+#
+# Purpose:
+# converts ipv4 address to a layer 2 multicast address
+#
+# Synopsis:
+# ipToLayer2Multicast <ipaddr>
+#
+# Arguments:
+# <ipaddr>
+# ipaddress in dotted form
+#
+# Return Values:
+# mac address in xx.xx.xx.xx.xx.xx form
+#
+# Description:
+#
+# Examples:
+# % ::ip::ipToLayer2Multicast 224.0.0.2
+# 01.00.5e.00.00.02
+# Sample Input:
+#
+# Sample Output:
+# Notes:
+#
+# See Also:
+#
+# End of Header
+
+proc ::ip::ipToLayer2Multicast { ipaddr } {
+ regexp "\[0-9\]+\.(\[0-9\]+)\.(\[0-9\]+)\.(\[0-9\]+)" $ipaddr junk ip2 ip3 ip4
+ #remove MSB of 2nd octet of IP address for mcast L2 addr
+ set mac2 [expr {$ip2 & 127}]
+ return [format "01.00.5e.%02x.%02x.%02x" $mac2 $ip3 $ip4]
+}
+
+
+##Procedure Header
+# Copyright (c) 2004 Cisco Systems, Inc.
+#
+# Name:
+# ::ip::ipHostFromPrefix
+#
+# Purpose:
+# gives back a host address from a prefix
+#
+# Synopsis:
+# ::ip::ipHostFromPrefix <prefix> [-exclude <list of prefixes>]
+#
+# Arguments:
+# <prefix>
+# prefix is <ipaddr>/<masklen>
+# -exclude <list of prefixes>
+# list if ipprefixes that host should not be in
+# Return Values:
+# ip address
+#
+# Description:
+#
+# Examples:
+# %::ip::ipHostFromPrefix 1.1.1.5/24
+# 1.1.1.1
+#
+# %::ip::ipHostFromPrefix 1.1.1.1/32
+# 1.1.1.1
+#
+#
+# Sample Input:
+#
+# Sample Output:
+# Notes:
+#
+# See Also:
+#
+# End of Header
+
+proc ::ip::ipHostFromPrefix { prefix args } {
+ set mask [mask $prefix]
+ set ipaddr [prefix $prefix]
+ if {[llength $args]} {
+ array set opts $args
+ } else {
+ if {$mask==32} {
+ return $ipaddr
+ } else {
+ return [intToString [expr {[toHex $ipaddr] + 1} ]]
+ }
+ }
+ set format {-ipv4}
+ # if we got here, then options were set
+ if {[info exists opts(-exclude)]} {
+ #basic algo is:
+ # 1. throw away prefixes that are less specific that $prefix
+ # 2. of remaining pfx, throw away prefixes that do not overlap
+ # 3. run reducetoAggregates on specific nets
+ # 4.
+
+ # 1. convert to hex format
+ set currHex [prefixToNative $prefix ]
+ set exclHex [prefixToNative $opts(-exclude) ]
+ # sort the prefixes by their mask, include the $prefix as a marker
+ # so we know from where to throw away prefixes
+ set sortedPfx [lsort -integer -index 1 [concat [list $currHex] $exclHex]]
+ # throw away prefixes that are less specific than $prefix
+ set specPfx [lrange $sortedPfx [expr {[lsearch -exact $sortedPfx $currHex] +1} ] end]
+
+ #2. throw away non-overlapping prefixes
+ set specPfx [isOverlapNative -all -inline \
+ [lindex $currHex 0 ] \
+ [lindex $currHex 1 ] \
+ $specPfx ]
+ #3. run reduce aggregates
+ set specPfx [reduceToAggregates $specPfx]
+
+ #4 now have to pick an address that overlaps with $currHex but not with
+ # $specPfx
+ # 4.1 find the largest prefix w/ most specific mask and go to the next net
+
+
+ # current ats tcl does not allow this in one command, so
+ # for now just going to grab the last prefix (list is already sorted)
+ set sPfx [lindex $specPfx end]
+ set startPfx $sPfx
+ # add currHex to specPfx
+ set oChkPfx [concat $specPfx [list $currHex]]
+
+
+ set notcomplete 1
+ set overflow 0
+ while {$notcomplete} {
+ #::ipMore::log::debug "doing nextnet on $sPfx"
+ set nextNet [nextNet [lindex $sPfx 0] [lindex $sPfx 1]]
+ #::ipMore::log::debug "trying $nextNet"
+ if {$overflow && ($nextNet > $startPfx)} {
+ #we've gone thru the entire net and didn't find anything.
+ return -code error [msgcat::mc "ip host could not be found in %s" $prefix]
+ break
+ }
+ set oPfx [isOverlapNative -all -inline \
+ $nextNet -1 \
+ $oChkPfx
+ ]
+ switch -exact [llength $oPfx] {
+ 0 {
+ # no overlap at all. meaning we have gone beyond the bounds of
+ # $currHex. need to overlap and try again
+ #::ipMore::log::debug {ipHostFromPrefix: overlap done}
+ set overflow 1
+ }
+ 1 {
+ #we've found what we're looking for. pick this address and exit
+ return [intToString $nextNet]
+ }
+ default {
+ # 2 or more overlaps, need to increment again
+ set sPfx [lindex $oPfx 0]
+ }
+ }
+ }
+ }
+}
+
+
+##Procedure Header
+# Copyright (c) 2004 Cisco Systems, Inc.
+#
+# Name:
+# ::ip::reduceToAggregates
+#
+# Purpose:
+# finds nets that overlap and filters out the more specifc nets
+#
+# Synopsis:
+# ::ip::reduceToAggregates <prefixList>
+#
+# Arguments:
+# <prefixList>
+# prefixList a list in the from of
+# is <ipaddr>/<masklen> or native format
+#
+# Return Values:
+# non-overlapping ip prefixes
+#
+# Description:
+#
+# Examples:
+#
+# % ::ip::reduceToAggregates {1.1.1.0/24 1.1.0.0/8 2.1.1.0/24 1.1.1.1/32 }
+# 1.0.0.0/8 2.1.1.0/24
+#
+# Sample Input:
+#
+# Sample Output:
+# Notes:
+#
+# See Also:
+#
+# End of Header
+
+proc ::ip::reduceToAggregates { prefixList } {
+ #find out format of $prefixeList
+ set dotConv 0
+ if {[llength [lindex $prefixList 0]]==1} {
+ #format is dotted form convert all prefixes to native form
+ set prefixList [ip::prefixToNative $prefixList]
+ set dotConv 1
+ }
+
+ set nonOverLapping $prefixList
+ while {1==1} {
+ set overlapFound 0
+ set remaining $nonOverLapping
+ set nonOverLapping {}
+ while {[llength $remaining]} {
+ set current [lvarpop remaining]
+ set overLap [ip::isOverlapNative [lindex $current 0] [lindex $current 1] $remaining]
+ if {$overLap} {
+ #there was a overlap find out which prefix has a the smaller mask, and keep that one
+ if {[lindex $current 1] > [lindex [lindex $remaining [expr {$overLap -1}]] 1]} {
+ #current has more restrictive mask, throw that prefix away
+ # keep other prefix
+ lappend nonOverLapping [lindex $remaining [expr {$overLap -1}]]
+ } else {
+ lappend nonOverLapping $current
+ }
+ lvarpop remaining [expr {$overLap -1}]
+ set overlapFound 1
+ } else {
+ #no overlap, keep all prefixes, don't touch the stuff in
+ # remaining, it is needed for other overlap checking
+ lappend nonOverLapping $current
+ }
+ }
+ if {$overlapFound==0} {break}
+ }
+ if {$dotConv} {return [nativeToPrefix $nonOverLapping]}
+ return $nonOverLapping
+}
+
+##Procedure Header
+# Copyright (c) 2004 Cisco Systems, Inc.
+#
+# Name:
+# ::ip::longestPrefixMatch
+#
+# Purpose:
+# given host IP finds longest prefix match from set of prefixes
+#
+# Synopsis:
+# ::ip::longestPrefixMatch <ipaddr> <prefixList> [-ipv4]
+#
+# Arguments:
+# <prefixList>
+# is list of <ipaddr> in native or dotted form
+# <ipaddr>
+# ip address in <ipprefix> format, dotted form, or integer form
+# -ipv4
+# the provided integer format addresses are in ipv4 format (default)
+#
+# Return Values:
+# <ipprefix> that is the most specific match to <ipaddr>
+#
+# Description:
+#
+# Examples:
+# % ::ip::longestPrefixMatch 1.1.1.1 {1.1.1.0/24 1.0.0.0/8 2.1.1.0/24 1.1.1.0/28 }
+# 1.1.1.0/28
+#
+# Sample Input:
+#
+# Sample Output:
+# Notes:
+#
+# See Also:
+#
+# End of Header
+
+proc ::ip::longestPrefixMatch { ipaddr prefixList args} {
+ set ipv4 1
+ while {[llength $args]} {
+ switch -- [lindex $args 0] {
+ -ipv4 {set args [lrange $args 1 end]}
+ default {
+ return -code error [msgcat::mc "option %s not supported" [lindex $args 0]]
+ }
+ }
+ }
+ #find out format of prefixes
+ set dotConv 0
+ if {[llength [lindex $prefixList 0]]==1} {
+ #format is dotted form convert all prefixes to native form
+ set prefixList [ip::prefixToNative $prefixList]
+ set dotConv 1
+ }
+ #sort so that most specific prefix is in the front
+ if {[llength [lindex [lindex $prefixList 0] 1]]} {
+ set prefixList [lsort -decreasing -integer -index 1 $prefixList]
+ } else {
+ set prefixList [list $prefixList]
+ }
+ if {![string is integer -strict $ipaddr]} {
+ set ipaddr [prefixToNative $ipaddr]
+ }
+ set best [ip::isOverlapNative -inline \
+ [lindex $ipaddr 0] [lindex $ipaddr 1] $prefixList]
+ if {$dotConv && [llength $best]} {
+ return [nativeToPrefix $best]
+ }
+ return $best
+}
+
+##Procedure Header
+# Copyright (c) 2004 Cisco Systems, Inc.
+#
+# Name:
+# ::ip::cmpDotIP
+#
+# Purpose:
+# helper function for dotted ip address for use in lsort
+#
+# Synopsis:
+# ::ip::cmpDotIP <ipaddr1> <ipaddr2>
+#
+# Arguments:
+# <ipaddr1> <ipaddr2>
+# prefix is in dotted ip address format
+#
+# Return Values:
+# -1 if ipaddr1 is less that ipaddr2
+# 1 if ipaddr1 is more that ipaddr2
+# 0 if ipaddr1 and ipaddr2 are equal
+#
+# Description:
+#
+# Examples:
+# % lsort -command ip::cmpDotIP {1.0.0.0 2.2.0.0 128.0.0.0 3.3.3.3}
+# 1.0.0.0 2.2.0.0 3.3.3.3 128.0.0.0
+#
+# Sample Input:
+#
+# Sample Output:
+# Notes:
+#
+# See Also:
+#
+# End of Header
+# ip address in <ipprefix> format, dotted form, or integer form
+
+if {![package vsatisfies [package provide Tcl] 8.4]} {
+ # 8.3+
+ proc ip::cmpDotIP {ipaddr1 ipaddr2} {
+ # convert dotted to list of integers
+ set ipaddr1 [split $ipaddr1 .]
+ set ipaddr2 [split $ipaddr2 .]
+ foreach a $ipaddr1 b $ipaddr2 {
+ #ipMore::log::debug "$ipInt1 $ipInt2"
+ if { $a < $b} {
+ return -1
+ } elseif {$a >$b} {
+ return 1
+ }
+ }
+ return 0
+ }
+} else {
+ # 8.4+
+ proc ip::cmpDotIP {ipaddr1 ipaddr2} {
+ # convert dotted to decimal
+ set ipInt1 [::ip::toHex $ipaddr1]
+ set ipInt2 [::ip::toHex $ipaddr2]
+ #ipMore::log::debug "$ipInt1 $ipInt2"
+ if { $ipInt1 < $ipInt2} {
+ return -1
+ } elseif {$ipInt1 >$ipInt2 } {
+ return 1
+ } else {
+ return 0
+ }
+ }
+}
+
+# Populate the array "maskLenToDotted" for fast lookups of mask to
+# dotted form.
+
+namespace eval ::ip {
+ variable maskLenToDotted
+ variable x
+
+ for {set x 0} {$x <33} {incr x} {
+ set maskLenToDotted($x) [intToString [maskToInt $x]]
+ }
+ unset x
+}
+
+##Procedure Header
+# Copyright (c) 2015 Martin Heinrich <martin.heinrich@frequentis.com>
+#
+# Name:
+# ::ip::distance
+#
+# Purpose:
+# Calculate integer distance between two IPv4 addresses (dotted form or int)
+#
+# Synopsis:
+# distance <ipaddr1> <ipaddr2>
+#
+# Arguments:
+# <ipaddr1>
+# <ipaddr2>
+# ip address
+#
+# Return Values:
+# integer distance (addr2 - addr1)
+#
+# Description:
+#
+# Examples:
+# % ::ip::distance 1.1.1.0 1.1.1.5
+# 5
+#
+# Sample Input:
+#
+# Sample Output:
+
+proc ::ip::distance {ip1 ip2} {
+ # use package ip for normalization
+ # XXX does not support ipv6
+ expr {[toInteger $ip2]-[toInteger $ip1]}
+}
+
+##Procedure Header
+# Copyright (c) 2015 Martin Heinrich <martin.heinrich@frequentis.com>
+#
+# Name:
+# ::ip::nextIp
+#
+# Purpose:
+# Increment the given IPv4 address by an offset.
+# Complement to 'distance'.
+#
+# Synopsis:
+# nextIp <ipaddr> ?<offset>?
+#
+# Arguments:
+# <ipaddr>
+# ip address
+#
+# <offset>
+# The integer to increment the address by.
+# Default is 1.
+#
+# Return Values:
+# The increment ip address.
+#
+# Description:
+#
+# Examples:
+# % ::ip::nextIp 1.1.1.0 5
+# 1.1.1.5
+#
+# Sample Input:
+#
+# Sample Output:
+
+proc ::ip::nextIp {ip {offset 1}} {
+ set int [toInteger $ip]
+ incr int $offset
+ set prot {}
+ # TODO if ipv4 then set prot -ipv4, but
+ # XXX intToString has -ipv4, but never returns ipv6
+ intToString $int ;# 8.5-ism, avoid: {*}$prot
+}
diff --git a/tcllib/modules/dns/ipMore.test b/tcllib/modules/dns/ipMore.test
new file mode 100644
index 0000000..246be8d
--- /dev/null
+++ b/tcllib/modules/dns/ipMore.test
@@ -0,0 +1,803 @@
+# ip.test -*- tcl -*-
+#
+# Tests for the Tcllib ip package
+#
+# -------------------------------------------------------------------------
+# See the file "license.terms" for information on usage and redistribution
+# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+# -------------------------------------------------------------------------
+# RCS: @(#) $Id: ipMore.test,v 1.4 2006/10/09 21:41:40 andreas_kupries Exp $
+
+# -------------------------------------------------------------------------
+
+source [file join \
+ [file dirname [file dirname [file join [pwd] [info script]]]] \
+ devtools testutilities.tcl]
+
+testsNeedTcl 8.2
+testsNeedTcltest 2.2
+
+testing {
+ useLocal ip.tcl ip
+}
+
+# -------------------------------------------------------------------------
+
+::tcltest::testConstraint Cextension \
+ [llength [info commands ::ip::prefixToNativec]]
+
+# -------------------------------------------------------------------------
+
+logger::setlevel critical
+
+namespace eval ::ip::test {
+
+ ::tcltest::test load-1 {} -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ } -result {}
+
+ ::tcltest::test ip::prefixToNativeTcl-1 {} -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ip::prefixToNativeTcl 1.1.1.0/24
+ } -result {0x01010100 0xffffff00}
+
+ ::tcltest::test ip::prefixToNativeTcl-2 {} -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ip::prefixToNativeTcl {1.1.1.0/24 1.0.0.0/8 2.1.1.0/24 1.1.1.1/32}
+ } -result {{0x01010100 0xffffff00} {0x01000000 0xff000000} {0x02010100 0xffffff00} {0x01010101 0xffffffff}}
+
+ ::tcltest::test ip::prefixToNativeTcl-3 {} -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ip::prefixToNativeTcl ""
+ } -result {}
+
+ ::tcltest::test ip::prefixToNativec-1 {} -setup {
+ } -constraints {
+ Cextension
+ } -cleanup {
+ } -body {
+ ip::prefixToNativec 1.1.1.0/24
+ } -result {0x01010100 0xFFFFFF00}
+
+ ::tcltest::test ip::prefixToNativec-2 {} -setup {
+ } -constraints {
+ Cextension
+ } -cleanup {
+ } -body {
+ ip::prefixToNativec 1.1.1.0/255.255.255.0
+ } -result {0x01010100 0xFFFFFF00}
+
+ ::tcltest::test ip::prefixToNativec-3 {} -setup {
+ } -constraints {
+ Cextension
+ } -cleanup {
+ } -body {
+ ip::prefixToNativec 1.1.1.0
+ } -result {0x01010100 0xFFFFFFFF}
+
+ ::tcltest::test ip::prefixToNativec-4 {} -setup {
+ } -constraints {
+ Cextension
+ } -cleanup {
+ } -body {
+ ip::prefixToNativec {1.1.1.0/24 1.0.0.0/8 2.1.1.0/24 1.1.1.1/32}
+ } -result {{0x01010100 0xFFFFFF00} {0x01000000 0xFF000000} {0x02010100 0xFFFFFF00} {0x01010101 0xFFFFFFFF}}
+
+ ::tcltest::test ip::prefixToNativec-5 {} -setup {
+ } -constraints {
+ Cextension
+ } -cleanup {
+ } -body {
+ ip::prefixToNativec {1.1.1.0/24 1.0AAF0/8 2.1.1.0/24 1.1.1.1/32}
+ } -result {} -returnCodes error
+
+ ::tcltest::test ip::prefixToNativec-6 {} -setup {
+ } -constraints {
+ Cextension
+ } -cleanup {
+ unset y
+ } -body {
+ set y {1.1.1.0/24 1.0.0.0/8 2.1.1.0/24 1.1.1.1/32}
+ ip::prefixToNativec $y
+ lindex $y 0
+ } -result {1.1.1.0/24}
+
+ ::tcltest::test ip::nativeToPrefix-1 {
+ single address test
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ip::nativeToPrefix {0x01010100 0xffffff00} -ipv4
+ } -result {1.1.1.0/24}
+
+ ::tcltest::test ip::nativeToPrefix-2 {
+ multi list test
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ip::nativeToPrefix {{0x01010100 0xffffff00} {0x01000000 0xff000000} {0x02010100 0xffffff00} {0x01010101 0xffffffff}} -ipv4
+ } -result {1.1.1.0/24 1.0.0.0/8 2.1.1.0/24 1.1.1.1/32}
+
+ ::tcltest::test ip::nativeToPrefix-3 {
+ 0 test
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ip::nativeToPrefix {0x0 0x0} -ipv4
+ } -result {0.0.0.0/0}
+
+ ::tcltest::test ip::nativeToPrefix-4 {
+ 0 test, check default is ipv4
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ip::nativeToPrefix {0x0 0x0}
+ } -result {0.0.0.0/0}
+
+ ::tcltest::test ip::toInteger-1 {} -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ::ip::toInteger 1.1.1.0
+ } -result {16843008}
+
+ ::tcltest::test ::ip::toHex-1 {} -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ::ip::toHex 1.1.1.0
+ } -result {0x01010100}
+
+
+
+
+
+ ::tcltest::test ::ip:broadcastAddress-1 {
+ dotted form
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ::ip::broadcastAddress 1.1.1.0/24
+ } -result {1.1.1.255}
+
+ ::tcltest::test ::ip:broadcastAddress-2 {
+ native form
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ::ip::broadcastAddress {0x01010100 0xffffff00}
+ } -result {0x010101ff}
+
+
+ ::tcltest::test ::ip:maskToLength-1 {
+ hexform
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ::ip::maskToLength 0xffffff00 -ipv4
+ } -result {24}
+
+ ::tcltest::test ::ip:maskToLength-2 {
+ dotted form
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ::ip::maskToLength 255.255.255.0
+ } -result {24}
+
+
+ ::tcltest::test ::ip:maskToLength-3 {
+ zero form
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ::ip::maskToLength 0.0.0.0
+ } -result {0}
+
+ ::tcltest::test ::ip:maskToLength-4 {
+ zero form
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ::ip::maskToLength 0x0 -ipv4
+ } -result {0}
+
+ ::tcltest::test ::ip:maskToLength-5 {
+ zero form, defualt is ipv4
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ::ip::maskToLength 0x0
+ } -result {0}
+
+
+ ::tcltest::test ::ip::lengthToMask-1 {
+ dotted form
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ::ip::lengthToMask 24 -ipv4
+ } -result {255.255.255.0}
+
+ ::tcltest::test ::ip::lengthToMask-2 {
+ dotted form, default is ipv4
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ::ip::lengthToMask 24
+ } -result {255.255.255.0}
+
+ ::tcltest::test ::ip:maskToInt-1 {
+ integer form
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ::ip::maskToInt 32
+ } -result {4294967295}
+
+
+ ::tcltest::test ::ip:maskToInt-2 {
+ dotted form
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ::ip::maskToInt 255.255.255.255
+ } -result {4294967295}
+
+ ::tcltest::test ::ip:intToString-1 {
+ convert 255.255.255.255
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ::ip::intToString 4294967295 -ipv4
+ } -result {255.255.255.255}
+
+ ::tcltest::test ::ip:intToString-2 {
+ convert hex to string
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ::ip::intToString 0x01010101 -ipv4
+ } -result {1.1.1.1}
+
+ ::tcltest::test ::ip:intToString-3 {
+ convert hex to string, default is ipv4
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ::ip::intToString 0x01010101
+ } -result {1.1.1.1}
+
+ ::tcltest::test ::ip:nextNet-1 {
+ 255.255.255.0/32 -> 255.255.255.1
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ::ip::nextNet 0xffffff00 0xffffffff -ipv4
+ } -result {0xffffff01}
+
+ ::tcltest::test ::ip:nextNet-2 {
+ 1.0.0.0/24 -> 1.0.1.0/24
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ip::nextNet 0x01000000 0xffffff00 -ipv4
+ } -result {0x01000100}
+
+
+ ::tcltest::test ::ip:nextNet-3 {
+ 1.1.28.0/24 -> 1.1.29.0
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ip::nextNet 0x01011c00 0xffffff00 -ipv4
+ } -result {0x01011d00}
+
+ ::tcltest::test ::ip:nextNet-4 {
+ 1.1.28.0/24 -> 1.1.29.0 by 1
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ip::nextNet 0x01011c00 0xffffff00 1 -ipv4
+ } -result {0x01011d00}
+
+
+ ::tcltest::test ::ip:nextNet-5 {
+ 1.1.1.1/32 -> 1.1.29.0 by 2
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ip::nextNet 0x01010101 [ip::maskToInt 32] 2 -ipv4
+ } -result {0x01010103}
+
+ ::tcltest::test ::ip:nextNet-6 {
+ 1.1.1.1/32 -> 1.1.29.0 by 2
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ip::nextNet 1.1.1.1 32 2
+ } -result {0x01010103}
+
+ ::tcltest::test ::ip:nextNet-7 {
+ 1.1.1.1/32 -> 1.1.29.0 by 2
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ip::nextNet 1.1.1.1 255.255.255.255 2
+ } -result {0x01010103}
+
+ ::tcltest::test ::ip:nextNet-8 {
+ 1.1.1.1/32 -> 1.1.29.0 by 2, default is ipv4
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ip::nextNet 0x01010101 [ip::maskToInt 32] 2
+ } -result {0x01010103}
+
+ ::tcltest::test ::ip:isOverlap-1 {
+ no overlap test
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ::ip::isOverlap 1.1.1.0/24 2.1.0.1/32
+ } -result {0}
+
+
+ ::tcltest::test ::ip:isOverlap-2 {
+ yes overlap test
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ::ip::isOverlap 1.1.1.0/24 2.1.0.1/32 1.1.1.1/32
+ } -result {1}
+
+
+ ::tcltest::test ::ip:isOverlapNative-1 {
+ no overlap test
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ::ip::isOverlapNative -ipv4 0x01010100 0xffffff00 {{0x02010001 0xffffffff}}
+ } -result {0}
+
+
+ ::tcltest::test ::ip:isOverlapNative-2 {
+ yes overlap test
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ set a [::ip::isOverlapNative -ipv4 0x01010100 0xffffff00 {{0x02010001 0xffffffff} {0x01010101 0xffffffff}} ]
+ expr $a > 0
+ } -result {1}
+
+ ::tcltest::test ::ip:isOverlapNativeTcl-1 {
+ no overlap test
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ::ip::isOverlapNativeTcl -ipv4 0x01010100 0xffffff00 {{0x02010001 0xffffffff}}
+ } -result {0}
+
+
+ ::tcltest::test ::ip:isOverlapNativeTcl-2 {
+ yes overlap test
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ set a [::ip::isOverlapNativeTcl -ipv4 0x01010100 0xffffff00 {{0x02010001 0xffffffff} {0x01010101 0xffffffff}}]
+ expr $a > 0
+ } -result {1}
+
+ ::tcltest::test ::ip:isOverlapNativeTcl-3 {
+ yes overlap test
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ set a [::ip::isOverlapNativeTcl -ipv4 0x01010100 0xffffff00 {{0x02010001 0xffffffff} {0x01010101 0xffffffff}}]
+ } -result {2}
+
+ ::tcltest::test ::ip:isOverlapNativeTcl-4 {
+ -all overlap test
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ set a [::ip::isOverlapNativeTcl -ipv4 -all 0x01010100 0xffffff00 {
+ {0x02010001 0xffffffff}
+ {0x01010101 0xffffffff}
+ {0x01010102 0xffffffff}
+ }]
+ } -result {2 3}
+
+ ::tcltest::test ::ip:isOverlapNativeTcl-5 {
+ -all overlap test with -inline
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ set a [::ip::isOverlapNativeTcl -ipv4 -all -inline 0x01010100 0xffffff00 {
+ {0x02010001 0xffffffff}
+ {0x01010101 0xffffffff}
+ {0x01010102 0xffffffff}
+ }]
+ } -result {{0x01010101 0xffffffff} {0x01010102 0xffffffff}}
+
+ ::tcltest::test ::ip:isOverlapNativeTcl-6 {
+ test with -inline
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ set a [::ip::isOverlapNativeTcl -ipv4 -inline 0x01010100 0xffffff00 {
+ {0x02010001 0xffffffff}
+ {0x01010101 0xffffffff}
+ {0x01010102 0xffffffff}
+ }]
+ } -result {{0x01010101 0xffffffff}}
+
+ ::tcltest::test ::ip:isOverlapNativeTcl-7 {
+ test with -all -inline one element return
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ set a [::ip::isOverlapNativeTcl -ipv4 -all -inline 0x01010100 0xffffff00 {
+ {0x02010001 0xffffffff}
+ {0x01010101 0xffffffff}
+ }]
+ } -result {{0x01010101 0xffffffff}}
+
+ ::tcltest::test ::ip:isOverlapNativeTcl-8 {
+ test with -inline
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ set a [::ip::isOverlapNativeTcl -ipv4 -inline 0x01010100 0xffffff00 {
+ {0x02010001 0xffffffff}
+ }]
+ } -result {}
+
+ ::tcltest::test ::ip:isOverlapNativec-1 {
+ no overlap test
+ } -setup {
+ } -constraints {
+ Cextension
+ } -cleanup {
+ } -body {
+ ::ip::isOverlapNativec -ipv4 0x01010100 0xffffff00 {{0x02010001 0xffffffff}}
+ } -result {0}
+
+
+ ::tcltest::test ::ip:isOverlapNativec-2 {
+ yes overlap test
+ } -setup {
+ } -constraints {
+ Cextension
+ } -cleanup {
+ } -body {
+ set a [::ip::isOverlapNativec -ipv4 0x01010100 0xffffff00 {{0x02010001 0xffffffff} {0x01010101 0xffffffff}}]
+ expr $a > 0
+ } -result {1}
+
+ ::tcltest::test ::ip:isOverlapNativec-3 {
+ yes overlap test
+ } -setup {
+ } -constraints {
+ Cextension
+ } -cleanup {
+ } -body {
+ set a [::ip::isOverlapNativec -ipv4 0x01010100 0xffffff00 {{0x02010001 0xffffffff} {0x01010101 0xffffffff}}]
+ } -result {2}
+
+ ::tcltest::test ::ip:isOverlapNativec-4 {
+ -all overlap test
+ } -setup {
+ } -constraints {
+ Cextension
+ } -cleanup {
+ } -body {
+ set a [::ip::isOverlapNativec -ipv4 -all 0x01010100 0xffffff00 {
+ {0x02010001 0xffffffff}
+ {0x01010101 0xffffffff}
+ {0x01010102 0xffffffff}
+ }]
+ } -result {2 3}
+
+ ::tcltest::test ::ip:isOverlapNativec-5 {
+ -all overlap test with -inline
+ } -setup {
+ } -constraints {
+ Cextension
+ } -cleanup {
+ } -body {
+ set a [::ip::isOverlapNativec -ipv4 -all -inline 0x01010100 0xffffff00 {
+ {0x02010001 0xffffffff}
+ {0x01010101 0xffffffff}
+ {0x01010102 0xffffffff}
+ }]
+ } -result {{0x01010101 0xffffffff} {0x01010102 0xffffffff}}
+
+ ::tcltest::test ::ip:isOverlapNativec-6 {
+ test with -inline
+ } -setup {
+ } -constraints {
+ Cextension
+ } -cleanup {
+ } -body {
+ set a [::ip::isOverlapNativec -ipv4 -inline 0x01010100 0xffffff00 {
+ {0x02010001 0xffffffff}
+ {0x01010101 0xffffffff}
+ {0x01010102 0xffffffff}
+ }]
+ } -result {{0x01010101 0xffffffff}}
+
+ ::tcltest::test ::ip:isOverlapNativec-7 {
+ test with -all -inline one element return
+ } -setup {
+ } -constraints {
+ Cextension
+ } -cleanup {
+ } -body {
+ set a [::ip::isOverlapNativec -ipv4 -all -inline 0x01010100 0xffffff00 {
+ {0x02010001 0xffffffff}
+ {0x01010101 0xffffffff}
+ }]
+ } -result {{0x01010101 0xffffffff}}
+
+ ::tcltest::test ::ip:isOverlapNativec-8 {
+ test with -inline with not overlaps, returns nothing
+ } -setup {
+ } -constraints {
+ Cextension
+ } -cleanup {
+ } -body {
+ set a [::ip::isOverlapNativec -ipv4 -inline 0x01010100 0xffffff00 {
+ {0x02010001 0xffffffff}
+ }]
+ } -result {}
+
+
+
+ ::tcltest::test ::ip:ipToLayer2Multicast-1 {
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ::ip::ipToLayer2Multicast 224.0.0.2
+ } -result {01.00.5e.00.00.02}
+
+ ::tcltest::test ::ip:ipHostFromPrefix-1 {
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ::ip::ipHostFromPrefix 1.1.1.1/32
+ } -result {1.1.1.1}
+
+ ::tcltest::test ::ip:ipHostFromPrefix-2 {
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ::ip::ipHostFromPrefix 1.1.1.0/24
+ } -result {1.1.1.1}
+
+ ::tcltest::test ::ip:ipHostFromPrefix-3 {
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ unset x
+ unset exlList
+ unset testPrefix
+ } -body {
+ set testPrefix 1.1.1.0/24
+ set exlList {
+ 1.1.1.18/32
+ 1.1.1.13/32
+ 1.1.1.17/32
+ 2.1.1.0/24
+ 1.1.0.0/16
+ 1.1.1.12/30
+ 1.1.1.4/30
+ }
+ set x [::ip::ipHostFromPrefix $testPrefix -exclude $exlList ]
+ ip::longestPrefixMatch $x [concat $exlList [list $testPrefix]] -ipv4
+ } -result {1.1.1.0/24}
+
+ ::tcltest::test ::ip:reduceToAggregates {
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ::ip::reduceToAggregates {1.1.1.0/24 1.1.0.0/8 2.1.1.0/24 1.1.1.1/32 }
+ } -result {1.0.0.0/8 2.1.1.0/24}
+
+ ::tcltest::test ::ip:longestPrefixMatch-1 {
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ::ip::longestPrefixMatch 1.1.1.1/32 {1.1.1.0/24 1.1.0.0/8 2.1.1.0/24 1.1.1.1/32 } -ipv4
+ } -result {1.1.1.1/32}
+
+ ::tcltest::test ::ip:longestPrefixMatch-2 {
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ::ip::longestPrefixMatch 1.1.1.1 {1.1.1.0/24 1.0.0.0/8 2.1.1.0/24 1.1.1.0/28 } -ipv4
+ } -result {1.1.1.0/28}
+
+ ::tcltest::test ::ip:longestPrefixMatch-3 {
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ::ip::longestPrefixMatch 1.1.1.1 {2.1.1.0/24 2.0.0.0/8} -ipv4
+ } -result {}
+
+ ::tcltest::test ::ip:longestPrefixMatch-4 {
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ unset x
+ unset y
+ } -body {
+ set x 128.0.0.2
+ set y {1.0.0.0/8 2.2.0.0/16 128.0.0.0/16 3.3.3.3/32}
+ ::ip::longestPrefixMatch $x $y -ipv4
+ # there was a problem when using varibles, it looked like
+ # tcl was modifying the original variables in an
+ # upvar fashion
+ ::ip::longestPrefixMatch $x $y -ipv4
+ } -result {128.0.0.0/16}
+
+ ::tcltest::test ::ip:longestPrefixMatch-5 {
+ check where the match list is only a single prefix (non-match case)
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ::ip::longestPrefixMatch 128.0.0.2 {1.0.0.0/8} -ipv4
+ } -result {}
+
+ ::tcltest::test ::ip:longestPrefixMatch-6 {
+ check where the match list is only a single prefix (match case)
+ } -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ::ip::longestPrefixMatch 128.0.0.2 {128.0.0.0/8} -ipv4
+ } -result {128.0.0.0/8}
+
+ ::tcltest::test ::ip:cmpDotIP-1 {
+ test sorting of cmpDotIP
+ } -setup {
+ set iplist {1.0.0.0 2.2.0.0 128.0.0.0 3.3.3.3}
+ } -constraints {
+ } -cleanup {
+ unset iplist
+ } -body {
+ set a [lsort -command ip::cmpDotIP $iplist]
+ } -result {1.0.0.0 2.2.0.0 3.3.3.3 128.0.0.0}
+
+
+ ::tcltest::test ip::distance-1 {basic distance} -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ::ip::distance 1.1.1.0 1.1.1.5
+ } -result 5
+
+ ::tcltest::test ip::distance-2 {distance, not enough args} -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ::ip::distance
+ } -returnCodes error -result {wrong # args: should be "::ip::distance ip1 ip2"}
+
+ ::tcltest::test ip::distance-3 {distance, too many args} -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ::ip::distance 1.1.1.1 1.1.1.5 1.1.1.19
+ } -returnCodes error -result {wrong # args: should be "::ip::distance ip1 ip2"}
+
+
+ ::tcltest::test ip::nextIp-1 {basic nextIp} -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ::ip::nextIp 1.1.1.0 5
+ } -result 1.1.1.5
+
+ ::tcltest::test ip::nextIp-2 {nextIp, not enough args} -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ::ip::nextIp
+ } -returnCodes error -result {wrong # args: should be "::ip::nextIp ip ?offset?"}
+
+ ::tcltest::test ip::nextIp-3 {nextIp, too many args} -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ::ip::nextIp 1.1.1.1 1.1.1.5 1.1.1.19
+ } -returnCodes error -result {wrong # args: should be "::ip::nextIp ip ?offset?"}
+
+ foreach {n delta ip1 ip2} {
+ 0 4294967295 0.0.0.0 255.255.255.255
+ 1 -4294967295 255.255.255.255 0.0.0.0
+ 2 7709 10.11.12.13 10.11.42.42
+ 3 -7709 10.11.42.42 10.11.12.13
+ 4 1994195353 54.229.115.42 173.194.116.195
+ 5 -1994195353 173.194.116.195 54.229.115.42
+ } {
+ ::tcltest::test ip::distance-4.$n {basic distance} -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ::ip::distance $ip1 $ip2
+ } -result $delta
+
+ ::tcltest::test ip::nextIp-4.$n {basic nextIp} -setup {
+ } -constraints {
+ } -cleanup {
+ } -body {
+ ::ip::nextIp $ip1 $delta
+ } -result $ip2
+ }
+
+}
+
+namespace delete ::ip::test
+
+testsuiteCleanup
+
+#
+# ;;; Local Variables:
+# ;;; mode: tcl
+# ;;; indent-tabs-mode:nil
+# ;;; End:
diff --git a/tcllib/modules/dns/ipMoreC.tcl b/tcllib/modules/dns/ipMoreC.tcl
new file mode 100644
index 0000000..a90b4b9
--- /dev/null
+++ b/tcllib/modules/dns/ipMoreC.tcl
@@ -0,0 +1,242 @@
+# Skip this for window and a specific version of Solaris
+#
+# This could do with an explanation -- why are we avoiding these platforms
+# and perhaps using critcl's platform::platform command might be better?
+#
+if {[string equal $::tcl_platform(platform) windows] ||
+ ([string equal $::tcl_platform(os) SunOS] &&
+ [string equal $::tcl_platform(osVersion) 5.6])
+} {
+ # avoid warnings about nothing to compile
+ critcl::ccode {
+ /* nothing to do */
+ }
+ return
+}
+
+package require critcl;
+
+namespace eval ::ip {
+
+critcl::ccode {
+#include <stdlib.h>
+#include <stdio.h>
+#include <tcl.h>
+#include <inttypes.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <sys/socket.h>
+}
+
+critcl::ccommand prefixToNativec {clientData interp objc objv} {
+ int elemLen, maskLen, ipLen, mask;
+ int rval,convertListc,i;
+ Tcl_Obj **convertListv;
+ Tcl_Obj *listPtr,*returnPtr, *addrList;
+ char *stringIP, *slashPos, *stringMask;
+ char v4HEX[11];
+
+ uint32_t inaddr;
+ listPtr = NULL;
+
+ /* printf ("\n in prefixToNativeC"); */
+ /* printf ("\n objc = %d",objc); */
+
+ if (objc != 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "<ipaddress>/<mask>");
+ return TCL_ERROR;
+ }
+
+
+ if (Tcl_ListObjGetElements (interp, objv[1],
+ &convertListc, &convertListv) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ returnPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
+ for (i = 0; i < convertListc; i++) {
+ /* need to create a duplicate here because when we modify */
+ /* the stringIP it'll mess up the original in the calling */
+ /* context */
+ addrList = Tcl_DuplicateObj(convertListv[i]);
+ stringIP = Tcl_GetStringFromObj(addrList, &elemLen);
+ listPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
+ /* printf ("\n ### %s ### string \n", stringIP); */
+ /* split the ip address and mask */
+ slashPos = strchr(stringIP, (int) '/');
+ if (slashPos == NULL) {
+ /* straight ip address without mask */
+ mask = 0xffffffff;
+ ipLen = strlen(stringIP);
+ } else {
+ /* ipaddress has the mask, handle the mask and seperate out the */
+ /* ip address */
+ /* printf ("\n ** %d ",(uintptr_t)slashPos); */
+ stringMask = slashPos +1;
+ maskLen =strlen(stringMask);
+ /* put mask in hex form */
+ if (maskLen < 3) {
+ mask = atoi(stringMask);
+ mask = (0xFFFFFFFF << (32 - mask)) & 0xFFFFFFFF;
+ } else {
+ /* mask is in dotted form */
+ if ((rval = inet_pton(AF_INET,stringMask,&mask)) < 1 ) {
+ Tcl_AddErrorInfo(interp, "\n bad format encountered in mask conversion");
+ return TCL_ERROR;
+ }
+ mask = htonl(mask);
+ }
+ ipLen = (uintptr_t)slashPos - (uintptr_t)stringIP;
+ /* divide the string into ip and mask portion */
+ *slashPos = '\0';
+ /* printf("\n %d %d %d %d", (uintptr_t)stringMask, maskLen, (uintptr_t)stringIP, ipLen); */
+ }
+ if ( (rval = inet_pton(AF_INET,stringIP,&inaddr)) < 1) {
+ Tcl_AddErrorInfo(interp,
+ "\n bad format encountered in ip conversion");
+ return TCL_ERROR;
+ };
+ inaddr = htonl(inaddr);
+ /* apply the mask the to the ip portion, just to make sure */
+ /* what we return is cleaned up */
+ inaddr = inaddr & mask;
+ sprintf(v4HEX,"0x%08X",inaddr);
+ /* printf ("\n\n ### %s",v4HEX); */
+ Tcl_ListObjAppendElement(interp, listPtr,
+ Tcl_NewStringObj(v4HEX,-1));
+ sprintf(v4HEX,"0x%08X",mask);
+ Tcl_ListObjAppendElement(interp, listPtr,
+ Tcl_NewStringObj(v4HEX,-1));
+ Tcl_ListObjAppendElement(interp, returnPtr, listPtr);
+ Tcl_DecrRefCount(addrList);
+ }
+
+ if (convertListc==1) {
+ Tcl_SetObjResult(interp,listPtr);
+ } else {
+ Tcl_SetObjResult(interp,returnPtr);
+ }
+
+ return TCL_OK;
+}
+
+critcl::ccommand isOverlapNativec {clientData interp objc objv} {
+ int i;
+ unsigned int ipaddr,ipMask, mask1mask2;
+ unsigned int ipaddr2,ipMask2;
+ int compareListc,comparePrefixMaskc;
+ int allSet,inlineSet,index;
+ Tcl_Obj **compareListv,**comparePrefixMaskv, *listPtr;
+ Tcl_Obj *result;
+ static CONST char *options[] = {
+ "-all", "-inline", "-ipv4", NULL
+ };
+ enum options {
+ OVERLAP_ALL, OVERLAP_INLINE, OVERLAP_IPV4
+ };
+
+ allSet = 0;
+ inlineSet = 0;
+ listPtr = NULL;
+
+ /* printf ("\n objc = %d",objc); */
+ if (objc < 3) {
+ Tcl_WrongNumArgs(interp, 1, objv, "?options? <hexIP> <hexMask> <hexList>");
+ return TCL_ERROR;
+ }
+ for (i = 1; i < objc-3; i++) {
+ if (Tcl_GetIndexFromObj(interp, objv[i], options, "option", 0, &index)
+ != TCL_OK) {
+ return TCL_ERROR;
+ }
+ switch (index) {
+ case OVERLAP_ALL:
+ allSet = 1;
+ /* printf ("\n all selected"); */
+ break;
+ case OVERLAP_INLINE:
+ inlineSet = 1;
+ /* printf ("\n inline selected"); */
+ break;
+ case OVERLAP_IPV4:
+ break;
+ }
+ }
+ /* options are parsed */
+
+ /* create return obj */
+ result = Tcl_GetObjResult (interp);
+
+ /* set ipaddr and ipmask */
+ Tcl_GetIntFromObj(interp,objv[objc-3],(int*)&ipaddr);
+ Tcl_GetIntFromObj(interp,objv[objc-2],(int*)&ipMask);
+
+ /* split the 3rd argument into <ipaddr> <mask> pairs */
+ if (Tcl_ListObjGetElements (interp, objv[objc-1], &compareListc, &compareListv) != TCL_OK) {
+ return TCL_ERROR;
+ }
+/* printf("comparing %x/%x \n",ipaddr,ipMask); */
+
+ if (allSet || inlineSet) {
+ listPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
+ }
+
+ for (i = 0; i < compareListc; i++) {
+ /* split the ipaddr2 and ipmask2 */
+ if (Tcl_ListObjGetElements (interp,
+ compareListv[i],
+ &comparePrefixMaskc,
+ &comparePrefixMaskv) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ if (comparePrefixMaskc != 2) {
+ Tcl_AddErrorInfo(interp,"need format {{<ipaddr> <mask>} {<ipad..}}");
+ return TCL_ERROR;
+ }
+ Tcl_GetIntFromObj(interp,comparePrefixMaskv[0],(int*)&ipaddr2);
+ Tcl_GetIntFromObj(interp,comparePrefixMaskv[1],(int*)&ipMask2);
+/* printf(" with %x/%x \n",ipaddr2,ipMask2); */
+ mask1mask2 = ipMask & ipMask2;
+/* printf(" mask1mask2 %x \n",mask1mask2); */
+/* printf(" ipaddr & mask1mask2 %x\n",ipaddr & mask1mask2); */
+/* printf(" ipaddr2 & mask1mask2 %x\n",ipaddr2 & mask1mask2); */
+ if ((ipaddr & mask1mask2) == (ipaddr2 & mask1mask2)) {
+ if (allSet) {
+ if (inlineSet) {
+ Tcl_ListObjAppendElement(interp, listPtr,
+ compareListv[i]);
+ } else {
+ /* printf("\n appending %d",i+1); */
+ Tcl_ListObjAppendElement(interp, listPtr,
+ Tcl_NewIntObj(i+1));
+ };
+ } else {
+ if (inlineSet) {
+ Tcl_ListObjAppendElement(interp, listPtr,
+ compareListv[i]);
+ Tcl_SetObjResult(interp,listPtr);
+ } else {
+ Tcl_SetIntObj (result, i+1);
+ }
+ return TCL_OK;
+ };
+ };
+ };
+
+ if (allSet || inlineSet) {
+ Tcl_SetObjResult(interp, listPtr);
+ return TCL_OK;
+ } else {
+ Tcl_SetIntObj (result, 0);
+ return TCL_OK;
+ }
+ return TCL_OK;
+
+
+
+}
+
+
+}
+
+# @sak notprovided ipMorec
+package provide ipMorec 1.0
diff --git a/tcllib/modules/dns/msgs/en.msg b/tcllib/modules/dns/msgs/en.msg
new file mode 100644
index 0000000..813cb9e
--- /dev/null
+++ b/tcllib/modules/dns/msgs/en.msg
@@ -0,0 +1,8 @@
+# -*- tcl -*-
+package require msgcat
+namespace import ::msgcat::*
+
+mcset en "option %s not supported" "option %s not supported"
+mcset en "option %s not supported" "option %s not supported"
+mcset en "not an ip mask: %s" "not an ip mask: %s"
+mcset en "ip host could not be found in %s" "ip host could not be found in %s"
diff --git a/tcllib/modules/dns/pkgIndex.tcl b/tcllib/modules/dns/pkgIndex.tcl
new file mode 100644
index 0000000..5f03e9c
--- /dev/null
+++ b/tcllib/modules/dns/pkgIndex.tcl
@@ -0,0 +1,9 @@
+# pkgIndex.tcl -
+#
+# $Id: pkgIndex.tcl,v 1.21 2010/08/16 17:35:18 andreas_kupries Exp $
+
+if {![package vsatisfies [package provide Tcl] 8.2]} {return}
+package ifneeded dns 1.3.5 [list source [file join $dir dns.tcl]]
+package ifneeded resolv 1.0.3 [list source [file join $dir resolv.tcl]]
+package ifneeded ip 1.3 [list source [file join $dir ip.tcl]]
+package ifneeded spf 1.1.1 [list source [file join $dir spf.tcl]]
diff --git a/tcllib/modules/dns/resolv.tcl b/tcllib/modules/dns/resolv.tcl
new file mode 100644
index 0000000..503be13
--- /dev/null
+++ b/tcllib/modules/dns/resolv.tcl
@@ -0,0 +1,249 @@
+# resolv.tcl - Copyright (c) 2002 Emmanuel Frecon <emmanuel@sics.se>
+#
+# Original Author -- Emmanuel Frecon - emmanuel@sics.se
+# Modified by Pat Thoyts <patthoyts@users.sourceforge.net>
+#
+# A super module on top of the dns module for host name resolution.
+# There are two services provided on top of the regular Tcl library:
+# Firstly, this module attempts to automatically discover the default
+# DNS server that is setup on the machine that it is run on. This
+# server will be used in all further host resolutions. Secondly, this
+# module offers a rudimentary cache. The cache is rudimentary since it
+# has no expiration on host name resolutions, but this is probably
+# enough for short lived applications.
+#
+# -------------------------------------------------------------------------
+# See the file "license.terms" for information on usage and redistribution
+# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+# -------------------------------------------------------------------------
+
+package require dns 1.0; # tcllib 1.3
+
+namespace eval ::resolv {
+ namespace export resolve init ignore hostname
+
+ variable R
+ if {![info exists R]} {
+ array set R {
+ initdone 0
+ dns ""
+ dnsdefault ""
+ ourhost ""
+ search {}
+ }
+ }
+}
+
+# -------------------------------------------------------------------------
+# Command Name -- ignore
+# Original Author -- Emmanuel Frecon - emmanuel@sics.se
+#
+# Remove a host name resolution from the cache, if present, so that the
+# next resolution will query the DNS server again.
+#
+# Arguments:
+# hostname - Name of host to remove from the cache.
+#
+proc ::resolv::ignore { hostname } {
+ variable Cache
+ catch {unset Cache($hostname)}
+ return
+}
+
+# -------------------------------------------------------------------------
+# Command Name -- init
+# Original Author -- Emmanuel Frecon - emmanuel@sics.se
+#
+# Initialise this module with a known host name. This host (not mandatory)
+# will become the default if the library was not able to find a DNS server.
+# This command can be called several times, its effect is double: actively
+# looking for the default DNS server setup on the running machine; and
+# emptying the host name resolution cache.
+#
+# Arguments:
+# defaultdns - Default DNS server
+#
+proc ::resolv::init { {defaultdns ""} {search {}}} {
+ variable R
+ variable Cache
+
+ # Clean the resolver cache
+ catch {unset Cache}
+
+ # Record the default DNS server and search list.
+ set R(dnsdefault) $defaultdns
+ set R(search) $search
+
+ # Now do some intelligent lookup. We do this on the current
+ # hostname to get a chance to get back some (full) information on
+ # ourselves. A previous version was using 127.0.0.1, not sure
+ # what is best.
+ set res [catch [list exec nslookup [info hostname]] lkup]
+ if { $res == 0 } {
+ set l [split $lkup]
+ set nl ""
+ foreach e $l {
+ if { [string length $e] > 0 } {
+ lappend nl $e
+ }
+ }
+
+ # Now, a lot of mixture to arrange so that hostname points at the
+ # DNS server that we should use for any further request. This
+ # code is complex, but was actually tested behind a firewall
+ # during the SITI Winter Conference 2003. There, strangly,
+ # nslookup returned an error but a DNS server was actually setup
+ # correctly...
+ set hostname ""
+ set len [llength $nl]
+ for { set i 0 } { $i < $len } { incr i } {
+ set e [lindex $nl $i]
+ if { [string match -nocase "*server*" $e] } {
+ set hostname [lindex $nl [expr {$i + 1}]]
+ if { [string match -nocase "UnKnown" $hostname] } {
+ set hostname ""
+ }
+ break
+ }
+ }
+
+ if { $hostname != "" } {
+ set R(dns) $hostname
+ } else {
+ for { set i 0 } { $i < $len } { incr i } {
+ set e [lindex $nl $i]
+ if { [string match -nocase "*address*" $e] } {
+ set hostname [lindex $nl [expr {$i + 1}]]
+ break
+ }
+ }
+ if { $hostname != "" } {
+ set R(dns) $hostname
+ }
+ }
+ }
+
+ if {$R(dns) == ""} {
+ set R(dns) $R(dnsdefault)
+ }
+
+
+ # Start again to find our full name
+ set ourhost ""
+ if {$res == 0} {
+ set dot [string first "." [info hostname]]
+ if { $dot < 0 } {
+ for { set i 0 } { $i < $len } { incr i } {
+ set e [lindex $nl $i]
+ if { [string match -nocase "*name*" $e] } {
+ set ourhost [lindex $nl [expr {$i + 1}]]
+ break
+ }
+ }
+ if { $ourhost == "" } {
+ if { ! [regexp {\d+\.\d+\.\d+\.\d+} $hostname] } {
+ set dot [string first "." $hostname]
+ set ourhost [format "%s%s" [info hostname] \
+ [string range $hostname $dot end]]
+ }
+ }
+ } else {
+ set ourhost [info hostname]
+ }
+ }
+
+ if {$ourhost == ""} {
+ set R(ourhost) [info hostname]
+ } else {
+ set R(ourhost) $ourhost
+ }
+
+
+ set R(initdone) 1
+
+ return $R(dns)
+}
+
+# -------------------------------------------------------------------------
+# Command Name -- resolve
+# Original Author -- Emmanuel Frecon - emmanuel@sics.se
+#
+# Resolve a host name to an IP address. This is a wrapping procedure around
+# the basic services of the dns library.
+#
+# Arguments:
+# hostname - Name of host
+#
+proc ::resolv::resolve { hostname } {
+ variable R
+ variable Cache
+
+ # Initialise if not already done. Auto initialisation cannot take
+ # any known DNS server (known to the caller)
+ if { ! $R(initdone) } { init }
+
+ # Check whether this is not simply a raw IP address. What about
+ # IPv6 ??
+ # - We don't have sockets in Tcl for IPv6 protocols - [PT]
+ #
+ if { [regexp {\d+\.\d+\.\d+\.\d+} $hostname] } {
+ return $hostname
+ }
+
+ # Look for hostname in the cache, if found return.
+ if { [array names ::resolv::Cache $hostname] != "" } {
+ return $::resolv::Cache($hostname)
+ }
+
+ # Scream if we don't have any DNS server setup, since we cannot do
+ # anything in that case.
+ if { $R(dns) == "" } {
+ return -code error "No dns server provided"
+ }
+
+ set R(retries) 0
+ set ip [Resolve $hostname]
+
+ # And store the result of resolution in our cache for further use.
+ set Cache($hostname) $ip
+
+ return $ip
+}
+
+# Description:
+# Attempt to resolve hostname via DNS. If the name cannot be resolved then
+# iterate through the search list appending each domain in turn until we
+# get one that succeeds.
+#
+proc ::resolv::Resolve {hostname} {
+ variable R
+ set t [::dns::resolve $hostname -server $R(dns)]
+ ::dns::wait $t; # wait with event processing
+ set status [dns::status $t]
+ if {$status == "ok"} {
+ set ip [lindex [::dns::address $t] 0]
+ ::dns::cleanup $t
+ } elseif {$status == "error"
+ && [::dns::errorcode $t] == 3
+ && $R(retries) < [llength $R(search)]} {
+ ::dns::cleanup $t
+ set suffix [lindex $R(search) $R(retries)]
+ incr R(retries)
+ set new [lindex [split $hostname .] 0].[string trim $suffix .]
+ set ip [Resolve $new]
+ } else {
+ set err [dns::error $t]
+ ::dns::cleanup $t
+ return -code error "dns error: $err"
+ }
+ return $ip
+}
+
+# -------------------------------------------------------------------------
+
+package provide resolv 1.0.3
+
+# -------------------------------------------------------------------------
+# Local Variables:
+# indent-tabs-mode: nil
+# End:
diff --git a/tcllib/modules/dns/spf.tcl b/tcllib/modules/dns/spf.tcl
new file mode 100644
index 0000000..a752c54
--- /dev/null
+++ b/tcllib/modules/dns/spf.tcl
@@ -0,0 +1,528 @@
+# spf.tcl - Copyright (C) 2004 Pat Thoyts <patthoyts@users.sourceforge.net>
+#
+# Sender Policy Framework
+#
+# http://www.ietf.org/internet-drafts/draft-ietf-marid-protocol-00.txt
+# http://spf.pobox.com/
+#
+# Some domains using SPF:
+# pobox.org - mx, a, ptr
+# oxford.ac.uk - include
+# gnu.org - ip4
+# aol.com - ip4, ptr
+# sourceforge.net - mx, a
+# altavista.com - exists, multiple TXT replies.
+# oreilly.com - mx, ptr, include
+# motleyfool.com - include (looping includes)
+#
+# -------------------------------------------------------------------------
+# See the file "license.terms" for information on usage and redistribution
+# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+# -------------------------------------------------------------------------
+
+package require Tcl 8.2; # tcl minimum version
+package require dns; # tcllib 1.3
+package require logger; # tcllib 1.3
+package require ip; # tcllib 1.7
+package require struct::list; # tcllib 1.7
+package require uri::urn; # tcllib 1.3
+
+namespace eval spf {
+ namespace export spf
+
+ variable uid
+ if {![info exists uid]} {set uid 0}
+
+ variable log
+ if {![info exists log]} {
+ set log [logger::init spf]
+ ${log}::setlevel warn
+ proc ${log}::stdoutcmd {level text} {
+ variable service
+ puts "\[[clock format [clock seconds] -format {%H:%M:%S}]\
+ $service $level\] $text"
+ }
+ }
+}
+
+# -------------------------------------------------------------------------
+# ip : ip address of the connecting host
+# domain : the domain to match
+# sender : full sender email address
+#
+proc ::spf::spf {ip domain sender} {
+ variable log
+
+ # 3.3: Initial processing
+ # If the sender address has no local part, set it to postmaster
+ set addr [split $sender @]
+ if {[set len [llength $addr]] == 0} {
+ return -code error -errorcode permanent "invalid sender address"
+ } elseif {$len == 1} {
+ set sender "postmaster@$sender"
+ }
+
+ # 3.4: Record lookup
+ set spf [SPF $domain]
+ if {[string equal $spf none]} {
+ return $spf
+ }
+
+ return [Spf $ip $domain $sender $spf]
+}
+
+proc ::spf::Spf {ip domain sender spf} {
+ variable log
+
+ # 3.4.1: Matching Version
+ if {![regexp {^v=spf(\d)\s+} $spf -> version]} {
+ return none
+ }
+
+ ${log}::debug "$spf"
+
+ if {$version != 1} {
+ return -code error -errorcode permanent \
+ "version mismatch: we only understand SPF 1\
+ this domain has provided version \"$version\""
+ }
+
+ set result ?
+ set seen_domains $domain
+ set explanation {denied}
+
+ set directives [lrange [split $spf { }] 1 end]
+ foreach directive $directives {
+ set prefix [string range $directive 0 0]
+ if {[string equal $prefix "+"] || [string equal $prefix "-"]
+ || [string equal $prefix "?"] || [string equal $prefix "~"]} {
+ set directive [string range $directive 1 end]
+ } else {
+ set prefix "+"
+ }
+
+ set cmd [string tolower [lindex [split $directive {:/=}] 0]]
+ set param [string range $directive [string length $cmd] end]
+
+ if {[info commands ::spf::_$cmd] == {}} {
+ # 6.1 Unrecognised directives terminate processing
+ # but unknown modifiers are ignored.
+ if {[string match "=*" $param]} {
+ continue
+ } else {
+ set result unknown
+ break
+ }
+ } else {
+ set r [catch {::spf::_$cmd $ip $domain $sender $param} res]
+ if {$r} {
+ if {$r == 2} {return $res};# deal with return -code return
+ if {[string equal $res "none"]
+ || [string equal $res "error"]
+ || [string equal $res "unknown"]} {
+ return $res
+ }
+ return -code error "error in \"$cmd\": $res"
+ }
+ if {$res} { set result $prefix }
+ }
+
+ ${log}::debug "$prefix $cmd\($param) -> $result"
+ if {[string equal $result "+"]} break
+ }
+
+ return $result
+}
+
+proc ::spf::loglevel {level} {
+ variable log
+ ${log}::setlevel $level
+}
+
+# get a guaranteed unique and non-present token id.
+proc ::spf::create_token {} {
+ variable uid
+ set id [incr uid]
+ while {[info exists [set token [namespace current]::$id]]} {
+ set id [incr uid]
+ }
+ return $token
+}
+
+# -------------------------------------------------------------------------
+#
+# SPF MECHANISM HANDLERS
+#
+# -------------------------------------------------------------------------
+
+# 4.1: The "all" mechanism is a test that always matches. It is used as the
+# rightmost mechanism in an SPF record to provide an explicit default
+#
+proc ::spf::_all {ip domain sender param} {
+ return 1
+}
+
+# 4.2: The "include" mechanism triggers a recursive SPF query.
+# The domain-spec is expanded as per section 8.
+proc ::spf::_include {ip domain sender param} {
+ variable log
+ upvar seen_domains Seen
+
+ if {![string equal [string range $param 0 0] ":"]} {
+ return -code error "dubious parameters for \"include\""
+ }
+ set r ?
+ set new_domain [Expand [string range $param 1 end] $ip $domain $sender]
+ if {[lsearch $Seen $new_domain] == -1} {
+ lappend Seen $new_domain
+ set spf [SPF $new_domain]
+ if {[string equal $spf none]} {
+ return $spf
+ }
+ set r [Spf $ip $new_domain $sender $spf]
+ }
+ return [string equal $r "+"]
+}
+
+# 4.4: This mechanism matches if <ip> is one of the target's
+# IP addresses.
+# e.g: a:smtp.example.com a:mail.%{d} a
+#
+proc ::spf::_a {ip domain sender param} {
+ variable log
+ foreach {testdomain bits} [ip::SplitIp [string trimleft $param :]] {}
+ if {[string length $testdomain] < 1} {
+ set testdomain $domain
+ } else {
+ set testdomain [Expand $testdomain $ip $domain $sender]
+ }
+ ${log}::debug " fetching A for $testdomain"
+ set dips [A $testdomain]; # get the IPs for the testdomain
+ foreach dip $dips {
+ ${log}::debug " compare: ${ip}/${bits} with ${dip}/${bits}"
+ if {[ip::equal $ip/$bits $dip/$bits]} {
+ return 1
+ }
+ }
+ return 0
+}
+
+# 4.5: This mechanism matches if the <sending-host> is one of the MX hosts
+# for a domain name.
+#
+proc ::spf::_mx {ip domain sender param} {
+ variable log
+ foreach {testdomain bits} [ip::SplitIp [string trimleft $param :]] {}
+ if {[string length $testdomain] < 1} {
+ set testdomain $domain
+ } else {
+ set testdomain [Expand $testdomain $ip $domain $sender]
+ }
+ ${log}::debug " fetching MX for $testdomain"
+ set mxs [MX $testdomain]
+
+ foreach mx $mxs {
+ set mx [lindex $mx 1]
+ set mxips [A $mx]
+ foreach mxip $mxips {
+ ${log}::debug " compare: ${ip}/${bits} with ${mxip}/${bits}"
+ if {[ip::equal $ip/$bits $mxip/$bits]} {
+ return 1
+ }
+ }
+ }
+ return 0
+}
+
+# 4.6: This mechanism tests if the <sending-host>'s name is within a
+# particular domain.
+#
+proc ::spf::_ptr {ip domain sender param} {
+ variable log
+ set validnames {}
+ if {[catch { set names [PTR $ip] } msg]} {
+ ${log}::debug " \"$ip\" $msg"
+ return 0
+ }
+ foreach name $names {
+ set addrs [A $name]
+ foreach addr $addrs {
+ if {[ip::equal $ip $addr]} {
+ lappend validnames $name
+ continue
+ }
+ }
+ }
+
+ ${log}::debug " validnames: $validnames"
+ set testdomain [Expand [string trimleft $param :] $ip $domain $sender]
+ if {$testdomain == {}} {
+ set testdomain $domain
+ }
+ foreach name $validnames {
+ if {[string match "*$testdomain" $name]} {
+ return 1
+ }
+ }
+
+ return 0
+}
+
+# 4.7: These mechanisms test if the <sending-host> falls into a given IP
+# network.
+#
+proc ::spf::_ip4 {ip domain sender param} {
+ variable log
+ foreach {network bits} [ip::SplitIp [string range $param 1 end]] {}
+ ${log}::debug " compare ${ip}/${bits} to ${network}/${bits}"
+ if {[ip::equal $ip/$bits $network/$bits]} {
+ return 1
+ }
+ return 0
+}
+
+# 4.6: These mechanisms test if the <sending-host> falls into a given IP
+# network.
+#
+proc ::spf::_ip6 {ip domain sender param} {
+ variable log
+ foreach {network bits} [ip::SplitIp [string range $param 1 end]] {}
+ ${log}::debug " compare ${ip}/${bits} to ${network}/${bits}"
+ if {[ip::equal $ip/$bits $network/$bits]} {
+ return 1
+ }
+ return 0
+}
+
+# 4.7: This mechanism is used to construct an arbitrary host name that is
+# used for a DNS A record query. It allows for complicated schemes
+# involving arbitrary parts of the mail envelope to determine what is
+# legal.
+#
+proc ::spf::_exists {ip domain sender param} {
+ variable log
+ set testdomain [Expand [string range $param 1 end] $ip $domain $sender]
+ ${log}::debug " checking existence of '$testdomain'"
+ if {[catch {A $testdomain}]} {
+ return 0
+ }
+ return 1
+}
+
+# 5.1: Redirected query
+#
+proc ::spf::_redirect {ip domain sender param} {
+ variable log
+ set new_domain [Expand [string range $param 1 end] $ip $domain $sender]
+ ${log}::debug ">> redirect to '$new_domain'"
+ set spf [SPF $new_domain]
+ if {![string equal $spf none]} {
+ set spf [Spf $ip $new_domain $sender $spf]
+ }
+ ${log}::debug "<< redirect returning '$spf'"
+ return -code return $spf
+}
+
+# 5.2: Explanation
+#
+proc ::spf::_exp {ip domain sender param} {
+ variable log
+ set new_domain [string range $param 1 end]
+ set exp [TXT $new_domain]
+ set exp [Expand $exp $ip $domain $sender]
+ ${log}::debug "exp expanded to \"$exp\""
+ # FIX ME: need to store this somehow.
+}
+
+# 5.3: Sender accreditation
+#
+proc ::spf::_accredit {ip domain sender param} {
+ variable log
+ set accredit [Expand [string range $param 1 end] $ip $domain $sender]
+ ${log}::debug " accreditation '$accredit'"
+ # We are not using this at the moment.
+ return 0
+}
+
+
+# 7: Macro expansion
+#
+proc ::spf::Expand {txt ip domain sender} {
+ variable log
+ set re {%\{[[:alpha:]](?:\d+)?r?[\+\-\.,/_=]*\}}
+ set txt [string map {\[ \\\[ \] \\\]} $txt]
+ regsub -all $re $txt {[ExpandMacro & $ip $domain $sender]} cmd
+ set cmd [string map {%% % %_ \ %- %20} $cmd]
+ return [subst -novariables $cmd]
+}
+
+proc ::spf::ExpandMacro {macro ip domain sender} {
+ variable log
+ set re {%\{([[:alpha:]])(\d+)?(r)?([\+\-\.,/_=]*)\}}
+ set C {} ; set T {} ; set R {}; set D {}
+ set r [regexp $re $macro -> C T R D]
+ if {$R == {}} {set R 0} else {set R 1}
+ set res $macro
+ if {$r} {
+ set enc [string is upper $C]
+ switch -exact -- [string tolower $C] {
+ s { set res $sender }
+ l {
+ set addr [split $sender @]
+ if {[llength $addr] < 2} {
+ set res postmaster
+ } else {
+ set res [lindex $addr 0]
+ }
+ }
+ o {
+ set addr [split $sender @]
+ if {[llength $addr] < 2} {
+ set res $sender
+ } else {
+ set res [lindex $addr 1]
+ }
+ }
+ h - d { set res $domain }
+ i {
+ set res [ip::normalize $ip]
+ if {[ip::is ipv6 $res]} {
+ # Convert 0000:0001 to 0.1
+ set t {}
+ binary scan [ip::Normalize $ip 6] c* octets
+ foreach octet $octets {
+ set hi [expr {($octet & 0xF0) >> 4}]
+ set lo [expr {$octet & 0x0F}]
+ lappend t [format %x $hi] [format %x $lo]
+ }
+ set res [join $t .]
+ }
+ }
+ v {
+ if {[ip::is ipv6 $ip]} {
+ set res ip6
+ } else {
+ set res "in-addr"
+ }
+ }
+ c {
+ set res [ip::normalize $ip]
+ if {[ip::is ipv6 $res]} {
+ set res [ip::contract $res]
+ }
+ }
+ r {
+ set s [socket -server {} -myaddr [info host] 0]
+ set res [lindex [fconfigure $s -sockname] 1]
+ close $s
+ }
+ t { set res [clock seconds] }
+ }
+ if {$T != {} || $R || $D != {}} {
+ if {$D == {}} {set D .}
+ set res [split $res $D]
+ if {$R} {
+ set res [struct::list::Lreverse $res]
+ }
+ if {$T != {}} {
+ incr T -1
+ set res [join [lrange $res end-$T end] $D]
+ }
+ set res [join $res .]
+ }
+ if {$enc} {
+ # URI encode the result.
+ set res [uri::urn::quote $res]
+ }
+ }
+ return $res
+}
+
+# -------------------------------------------------------------------------
+#
+# DNS helper procedures.
+#
+# -------------------------------------------------------------------------
+
+proc ::spf::Resolve {domain type resultproc} {
+ if {[info commands $resultproc] == {}} {
+ return -code error "invalid arg: \"$resultproc\" must be a command"
+ }
+ set tok [dns::resolve $domain -type $type]
+ dns::wait $tok
+ set errorcode NONE
+ if {[string equal [dns::status $tok] "ok"]} {
+ set result [$resultproc $tok]
+ set code ok
+ } else {
+ set result [dns::error $tok]
+ set errorcode [dns::errorcode $tok]
+ set code error
+ }
+ dns::cleanup $tok
+ return -code $code -errorcode $errorcode $result
+}
+
+# 3.4: Record lookup
+proc ::spf::SPF {domain} {
+ set txt ""
+ if {[catch {Resolve $domain SPF ::dns::result} spf]} {
+ set code $::errorCode
+ ${log}::debug "error fetching SPF record: $r"
+ switch -exact -- $code {
+ 3 { return -code return [list - "Domain Does Not Exist"] }
+ 2 { return -code error -errorcode temporary $spf }
+ }
+ set txt none
+ } else {
+ foreach res $spf {
+ set ndx [lsearch $res rdata]
+ incr ndx
+ if {$ndx != 0} {
+ append txt [string range [lindex $res $ndx] 1 end]
+ }
+ }
+ }
+ return $txt
+}
+
+proc ::spf::TXT {domain} {
+ set r [Resolve $domain TXT ::dns::result]
+ set txt ""
+ foreach res $r {
+ set ndx [lsearch $res rdata]
+ incr ndx
+ if {$ndx != 0} {
+ append txt [string range [lindex $res $ndx] 1 end]
+ }
+ }
+ return $txt
+}
+
+proc ::spf::A {name} {
+ return [Resolve $name A ::dns::address]
+}
+
+
+proc ::spf::AAAA {name} {
+ return [Resolve $name AAAA ::dns::address]
+}
+
+proc ::spf::PTR {addr} {
+ return [Resolve $addr A ::dns::name]
+}
+
+proc ::spf::MX {domain} {
+ set r [Resolve $domain MX ::dns::name]
+ return [lsort -index 0 $r]
+}
+
+
+# -------------------------------------------------------------------------
+
+package provide spf 1.1.1
+
+# -------------------------------------------------------------------------
+# Local Variables:
+# indent-tabs-mode: nil
+# End:
diff --git a/tcllib/modules/dns/spf.test b/tcllib/modules/dns/spf.test
new file mode 100644
index 0000000..3ba3bb9
--- /dev/null
+++ b/tcllib/modules/dns/spf.test
@@ -0,0 +1,244 @@
+# spf.test - Copyright (C) 2004 Pat Thoyts <patthoyts@users.sourceforge.net>
+#
+# Tests for the Tcllib SPF package
+#
+# -------------------------------------------------------------------------
+# See the file "license.terms" for information on usage and redistribution
+# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+# -------------------------------------------------------------------------
+# RCS: @(#) $Id: spf.test,v 1.8 2007/08/22 20:37:50 andreas_kupries Exp $
+
+# -------------------------------------------------------------------------
+
+source [file join \
+ [file dirname [file dirname [file join [pwd] [info script]]]] \
+ devtools testutilities.tcl]
+
+testsNeedTcl 8.2
+testsNeedTcltest 1.0
+
+support {
+ useLocal dns.tcl dns; # tcllib 1.3
+ useLocal ip.tcl ip; # tcllib 1.7
+ use log/logger.tcl logger; # tcllib 1.3
+ use struct/list.tcl struct::list; # tcllib 1.7
+ use uri/uri.tcl uri; # - clear scheme registry
+ use uri/urn-scheme.tcl uri::urn; # tcllib 1.3
+}
+testing {
+ useLocal spf.tcl spf
+}
+
+# -------------------------------------------------------------------------
+# Helpers
+# -------------------------------------------------------------------------
+
+# These tests do not make any network calls. Instead we emulate the
+# DNS query results wiht the following functions.
+
+foreach cmd [list SPF TXT A PTR MX] {
+ catch {rename ::spf::$cmd ::spf::tmp_$cmd}
+}
+proc ::spf::A {name} { return 192.0.2.3 }
+proc ::spf::AAAA {name} { return 5f05:2000:80ad:5800::1 }
+proc ::spf::PTR {addr} { return mx.example.org }
+proc ::spf::MX {domain} { return {{10 mx1.example.org} {20 mx2.example.org}} }
+proc ::spf::TXT {domain} { return "Only mail from local hosts permitted." }
+proc ::spf::SPF {domain} { return "v=spf1 ?all" }
+set email strong-bad@email.example.com
+
+# -------------------------------------------------------------------------
+# Tests
+# -------------------------------------------------------------------------
+
+test spf-1.1 {a directive: fallthrough} {
+ list [catch {
+ spf::Spf 192.168.0.1 email.example.com $::email "v=spf1 a -all"
+ } r] $r
+} {0 -}
+
+test spf-1.2 {a directive: fallthrough} {
+ list [catch {
+ spf::Spf 192.168.0.1 email.example.com $::email "v=spf1 a ?all"
+ } r] $r
+} {0 ?}
+
+test spf-1.3 {a directive: matching subnet} {
+ list [catch {
+ spf::Spf 192.0.2.1 email.example.com $::email "v=spf1 a/24 ?all"
+ } r] $r
+} {0 +}
+
+test spf-1.4 {a directive: rejected matching subnet} {
+ list [catch {
+ spf::Spf 192.0.2.1 email.example.com $::email "v=spf1 -a/24 ?all"
+ } r] $r
+} {0 ?}
+
+test spf-1.5 {a directive: match host} {
+ list [catch {
+ spf::Spf 192.0.2.3 email.example.com $::email "v=spf1 a ?all"
+ } r] $r
+} {0 +}
+
+test spf-2.1 {mx directive: fail mx} {
+ list [catch {
+ spf::Spf 192.168.0.1 email.example.com $::email "v=spf1 mx ?all"
+ } r] $r
+} {0 ?}
+
+test spf-2.2 {mx directive: match mx subnet} {
+ list [catch {
+ spf::Spf 192.0.2.1 email.example.com $::email "v=spf1 mx/24 ?all"
+ } r] $r
+} {0 +}
+
+test spf-2.3 {mx directive: fail match explict mx} {
+ list [catch {
+ spf::Spf 192.168.0.1 email.example.com $::email \
+ "v=spf1 mx:mail.local.net ?all"
+ } r] $r
+} {0 ?}
+
+test spf-2.4 {mx directive: match explict mx} {
+ list [catch {
+ spf::Spf 192.0.2.1 email.example.com $::email \
+ "v=spf1 mx:mail.local.net/24 ?all"
+ } r] $r
+} {0 +}
+
+test spf-2.5 {mx directive: match explict mx} {
+ list [catch {
+ spf::Spf 192.0.2.3 email.example.com $::email \
+ "v=spf1 mx:mx2.example.org ?all"
+ } r] $r
+} {0 +}
+
+test spf-3.1 {ptr directive} {
+ list [catch {
+ spf::Spf 192.0.2.3 email.example.com $::email "v=spf1 ptr ?all"
+ } r] $r
+} {0 ?}
+
+test spf-3.2 {ptr directive} {
+ list [catch {
+ spf::Spf 192.0.2.3 email.example.com $::email "v=spf1 ptr ?all"
+ } r] $r
+} {0 ?}
+
+test spf-3.3 {ptr directive} {
+ list [catch {
+ spf::Spf 192.0.2.3 email.example.com $::email \
+ "v=spf1 ptr:example.org ?all"
+ } r] $r
+} {0 +}
+
+test spf-3.4 {ptr directive} {
+ list [catch {
+ spf::Spf 192.0.2.3 email.example.com $::email \
+ "v=spf1 ptr:example.com ?all"
+ } r] $r
+} {0 ?}
+
+test spf-4.1 {ip4 directive} {
+ list [catch {
+ spf::Spf 192.168.2.3 email.example.com $::email \
+ "v=spf1 ip4:192.0.2.3/32 ?all"
+ } r] $r
+} {0 ?}
+
+test spf-4.2 {ip4 directive} {
+ list [catch {
+ spf::Spf 192.0.2.3 email.example.com $::email \
+ "v=spf1 ip4:192.0.2.0/24 ?all"
+ } r] $r
+} {0 +}
+
+test spf-4.3 {ip4 directive} {
+ list [catch {
+ spf::Spf 192.0.2.3 email.example.com $::email \
+ "v=spf1 ip4:192.0.0.0/16 ?all"
+ } r] $r
+} {0 +}
+
+test spf-4.4 {ip4 directive} {
+ list [catch {
+ spf::Spf 192.255.2.3 email.example.com $::email \
+ "v=spf1 ip4:192.0.0.0/16 ?all"
+ } r] $r
+} {0 ?}
+
+test spf-4.5 {ip4 directive} {
+ list [catch {
+ spf::Spf 192.0.2.3 email.example.com $::email \
+ "v=spf1 ip4:192.0/16 ?all"
+ } r] $r
+} {0 +}
+
+# -------------------------------------------------------------------------
+# Macros language tests
+# These are all taken from the specification document.
+
+set Data {
+ %{s} strong-bad@email.example.com
+ %{o} email.example.com
+ %{d} email.example.com
+ %{d4} email.example.com
+ %{d3} email.example.com
+ %{d2} example.com
+ %{d1} com
+ %{dr} com.example.email
+ %{d2r} example.email
+ %{l} strong-bad
+ %{l-} strong.bad
+ %{lr} strong-bad
+ %{lr-} bad.strong
+ %{l1r-} strong
+ %{ir}.%{v}._spf.%{d2} 3.2.0.192.in-addr._spf.example.com
+ %{lr-}.lp._spf.%{d2} bad.strong.lp._spf.example.com
+
+ %{lr-}.lp.%{ir}.%{v}._spf.%{d2}
+ bad.strong.lp.3.2.0.192.in-addr._spf.example.com
+
+ %{ir}.%{v}.%{l1r-}.lp._spf.%{d2}
+ 3.2.0.192.in-addr.strong.lp._spf.example.com
+
+ %{d2}.trusted-domains.example.net
+ example.com.trusted-domains.example.net
+}
+set n 0
+foreach {macro check} $Data {
+ test spf-5.[incr n] [list spf macro language $macro] {
+ list [catch {
+ ::spf::Expand $macro 192.0.2.3 email.example.com $::email
+ } msg] $msg
+ } [list 0 $check]
+}
+
+set Data {
+ %{ir}.%{v}._spf.%{d2}
+ 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.5.d.a.0.8.0.0.0.2.5.0.f.5.ip6._spf.example.com
+}
+set n 0
+foreach {macro check} $Data {
+ test spf-6.0 [list spf macro language ipv6] {
+ list [catch {
+ ::spf::Expand $macro 5f05:2000:80ad:5800::1 \
+ email.example.com $::email
+ } msg] $msg
+ } [list 0 $check]
+}
+
+# -------------------------------------------------------------------------
+
+foreach cmd [list SPF TXT A PTR MX] {
+ catch {rename ::spf::$cmd {}}
+ catch {rename ::spf::tmp_$cmd ::spf::$cmd}
+}
+
+testsuiteCleanup
+
+# Local Variables:
+# mode: tcl
+# indent-tabs-mode: nil
+# End:
diff --git a/tcllib/modules/dns/tcllib_dns.man b/tcllib/modules/dns/tcllib_dns.man
new file mode 100644
index 0000000..c0cae7f
--- /dev/null
+++ b/tcllib/modules/dns/tcllib_dns.man
@@ -0,0 +1,242 @@
+[vset DNS_VERSION 1.3.5]
+[manpage_begin dns n [vset DNS_VERSION]]
+[see_also resolver(5)]
+[keywords DNS]
+[keywords {domain name service}]
+[keywords resolver]
+[keywords {rfc 1034}]
+[keywords {rfc 1035}]
+[keywords {rfc 1886}]
+[copyright {2002, Pat Thoyts}]
+[moddesc {Domain Name Service}]
+[titledesc {Tcl Domain Name Service Client}]
+[category Networking]
+[require Tcl 8.2]
+[require dns [opt [vset DNS_VERSION]]]
+[description]
+[para]
+
+The dns package provides a Tcl only Domain Name Service client. You
+should refer to (1) and (2) for information about the DNS protocol or
+read resolver(3) to find out how the C library resolves domain names.
+
+The intention of this package is to insulate Tcl scripts
+from problems with using the system library resolver for slow name servers.
+It may or may not be of practical use. Internet name resolution is a
+complex business and DNS is only one part of the resolver. You may
+find you are supposed to be using hosts files, NIS or WINS to name a
+few other systems. This package is not a substitute for the C library
+resolver - it does however implement name resolution over DNS.
+
+The package also extends the package [package uri] to support DNS URIs
+(4) of the form [uri dns:what.host.com] or
+[uri dns://my.nameserver/what.host.com]. The [cmd dns::resolve]
+command can handle DNS URIs or simple domain names as a query.
+
+[para]
+
+[emph Note:] The package defaults to using DNS over TCP
+connections. If you wish to use UDP you will need to have the
+[package tcludp] package installed and have a version that
+correctly handles binary data (> 1.0.4).
+This is available at [uri http://tcludp.sourceforge.net/].
+If the [package udp] package is present then UDP will be used by default.
+
+[section COMMANDS]
+
+[list_begin definitions]
+
+[call [cmd ::dns::resolve] [arg query] [opt [arg "options"]]]
+
+Resolve a domain name using the [term DNS] protocol. [arg query] is
+the domain name to be lookup up. This should be either a fully
+qualified domain name or a DNS URI.
+
+[list_begin definitions]
+[def "[cmd -nameserver] [arg hostname] or [cmd -server] [arg hostname]"]
+ Specify an alternative name server for this request.
+[def "[cmd -protocol] [arg tcp|udp]"]
+ Specify the network protocol to use for this request. Can be one of
+ [arg tcp] or [arg udp].
+[def "[cmd -port] [arg portnum]"]
+ Specify an alternative port.
+[def "[cmd -search] [arg domainlist]"]
+[def "[cmd -timeout] [arg milliseconds]"]
+ Override the default timeout.
+[def "[cmd -type] [arg TYPE]"]
+ Specify the type of DNS record you are interested in. Valid values
+ are A, NS, MD, MF, CNAME, SOA, MB, MG, MR, NULL, WKS, PTR, HINFO,
+ MINFO, MX, TXT, SPF, SRV, AAAA, AXFR, MAILB, MAILA and *.
+ See RFC1035 for details about the return values.
+ See [uri http://spf.pobox.com/] about SPF.
+ See (3) about AAAA records and RFC2782 for details of SRV records.
+
+[def "[cmd -class] [arg CLASS]"]
+ Specify the class of domain name. This is usually IN but may be one
+ of IN for internet domain names, CS, CH, HS or * for any class.
+[def "[cmd -recurse] [arg boolean]"]
+ Set to [arg false] if you do not want the name server to recursively
+ act upon your request. Normally set to [arg true].
+[def "[cmd -command] [arg procname]"]
+ Set a procedure to be called upon request completion. The procedure
+ will be passed the token as its only argument.
+[list_end]
+
+[para]
+[call [cmd ::dns::configure] [opt [arg "options"]]]
+
+The [cmd ::dns::configure] command is used to setup the dns
+package. The server to query, the protocol and domain search path are
+all set via this command. If no arguments are provided then a list of
+all the current settings is returned. If only one argument then it
+must the the name of an option and the value for that option is
+returned.
+
+[list_begin definitions]
+[def "[cmd -nameserver] [arg hostname]"]
+ Set the default name server to be used by all queries. The default is
+ [term localhost].
+[def "[cmd -protocol] [arg tcp|udp]"]
+ Set the default network protocol to be used. Default is [arg tcp].
+[def "[cmd -port] [arg portnum]"]
+ Set the default port to use on the name server. The default is 53.
+[def "[cmd -search] [arg domainlist]"]
+ Set the domain search list. This is currently not used.
+[def "[cmd -timeout] [arg milliseconds]"]
+ Set the default timeout value for DNS lookups. Default is 30 seconds.
+[def "[cmd -loglevel] [arg level]"]
+ Set the log level used for emitting diagnostic messages from this
+ package. The default is [term warn]. See the [package log] package
+ for details of the available levels.
+[list_end]
+
+[para]
+[call [cmd ::dns::name] [arg token]]
+ Returns a list of all domain names returned as an answer to your query.
+
+[para]
+[call [cmd ::dns::address] [arg token]]
+ Returns a list of the address records that match your query.
+
+[para]
+[call [cmd ::dns::cname] [arg token]]
+ Returns a list of canonical names (usually just one) matching your query.
+
+[para]
+[call [cmd ::dns::result] [arg token]]
+ Returns a list of all the decoded answer records provided for your
+ query. This permits you to extract the result for more unusual query types.
+
+[para]
+[call [cmd ::dns::status] [arg token]]
+ Returns the status flag. For a successfully completed query this will be
+ [emph ok]. May be [emph error] or [emph timeout] or [emph eof].
+ See also [cmd ::dns::error]
+
+[para]
+[call [cmd ::dns::error] [arg token]]
+ Returns the error message provided for requests whose status is [emph error].
+ If there is no error message then an empty string is returned.
+
+[para]
+[call [cmd ::dns::reset] [arg token]]
+ Reset or cancel a DNS query.
+
+[para]
+[call [cmd ::dns::wait] [arg token]]
+ Wait for a DNS query to complete and return the status upon completion.
+
+[para]
+[call [cmd ::dns::cleanup] [arg token]]
+ Remove all state variables associated with the request.
+
+[para]
+[call [cmd ::dns::nameservers]]
+
+Attempts to return a list of the nameservers currently configured
+for the users system. On a unix machine this parses the
+/etc/resolv.conf file for nameservers (if it exists) and on Windows
+systems we examine certain parts of the registry. If no nameserver can
+be found then the loopback address (127.0.0.1) is used as a default.
+
+[list_end]
+
+[comment { ----------------------------------------------------------- }]
+
+[section EXAMPLES]
+
+[para]
+[example {
+% set tok [dns::resolve www.tcl.tk]
+::dns::1
+% dns::status $tok
+ok
+% dns::address $tok
+199.175.6.239
+% dns::name $tok
+www.tcl.tk
+% dns::cleanup $tok
+}]
+
+[para]
+Using DNS URIs as queries:
+[example {
+% set tok [dns::resolve "dns:tcl.tk;type=MX"]
+% set tok [dns::resolve "dns://l.root-servers.net/www.tcl.tk"]
+}]
+
+[para]
+Reverse address lookup:
+[example {
+% set tok [dns::resolve 127.0.0.1]
+::dns::1
+% dns::name $tok
+localhost
+% dns::cleanup $tok
+}]
+
+[comment { ----------------------------------------------------------- }]
+
+[section {REFERENCES}]
+
+[list_begin enumerated]
+
+[enum]
+ Mockapetris, P., "Domain Names - Concepts and Facilities",
+ RFC 1034, November 1987.
+ ([uri http://www.ietf.org/rfc/rfc1034.txt])
+
+[enum]
+ Mockapetris, P., "Domain Names - Implementation and Specification",
+ RFC 1035, November 1087.
+ ([uri http://www.ietf.org/rfc/rfc1035.txt])
+
+[enum]
+ Thompson, S. and Huitema, C., "DNS Extensions to support IP version 6",
+ RFC 1886, December 1995.
+ ([uri http://www.ietf.org/rfc/rfc1886.txt])
+
+[enum]
+ Josefsson, S., "Domain Name System Uniform Resource Identifiers",
+ Internet-Draft, October 2003,
+ ([uri http://www.ietf.org/internet-drafts/draft-josefsson-dns-url-09.txt])
+
+[enum]
+ Gulbrandsen, A., Vixie, P. and Esibov, L.,
+ "A DNS RR for specifying the location of services (DNS SRV)",
+ RFC 2782, February 2000,
+ ([uri http://www.ietf.org/rfc/rfc2782.txt])
+
+[enum]
+ Ohta, M. "Incremental Zone Transfer in DNS",
+ RFC 1995, August 1996,
+ ([uri http://www.ietf.org/rfc/rfc1995.txt])
+
+[list_end]
+
+[section AUTHORS]
+Pat Thoyts
+
+[vset CATEGORY dns]
+[include ../doctools2base/include/feedback.inc]
+[manpage_end]
diff --git a/tcllib/modules/dns/tcllib_ip.man b/tcllib/modules/dns/tcllib_ip.man
new file mode 100644
index 0000000..66d0e24
--- /dev/null
+++ b/tcllib/modules/dns/tcllib_ip.man
@@ -0,0 +1,451 @@
+[vset IP_VERSION 1.3]
+[manpage_begin tcllib_ip n [vset IP_VERSION]]
+[see_also inet(3)]
+[see_also ip(7)]
+[see_also ipv6(7)]
+[keywords {internet address}]
+[keywords ip]
+[keywords ipv4]
+[keywords ipv6]
+[keywords {rfc 3513}]
+[copyright {2004, Pat Thoyts}]
+[copyright {2005 Aamer Akhter <aakhter@cisco.com>}]
+[moddesc {Domain Name Service}]
+[titledesc {IPv4 and IPv6 address manipulation}]
+[category Networking]
+[require Tcl 8.2]
+[require ip [opt [vset IP_VERSION]]]
+[description]
+[para]
+
+This package provides a set of commands to help in parsing, displaying
+and comparing internet addresses. The package can handle both IPv4 (1)
+and IPv6 (2) address types.
+
+[section COMMANDS]
+
+[list_begin definitions]
+
+[call [cmd ::ip::version] [arg address]]
+
+Returns the protocol version of the address (4 or 6) or 0 if the
+address is neither IPv4 or IPv6.
+
+[call [cmd ::ip::is] [arg class] [arg address]]
+
+Returns true if the address is a member of the given protocol
+class. The class parameter may be either [arg ipv4] or [arg ipv6]
+This is effectively a boolean equivalent of the [cmd version]
+command. The [arg class] argument may be shortened to [arg 4] or
+[arg 6].
+
+[call [cmd ::ip::equal] [arg address] [arg address]]
+
+Compare two address specifications for equivalence. The arguments are
+normalized and the address prefix determined (if a mask is
+supplied). The normalized addresses are then compared bit-by-bit and
+the procedure returns true if they match.
+
+[call [cmd ::ip::normalize] [arg address]]
+
+Convert an IPv4 or IPv6 address into a fully expanded version. There
+are various shorthand ways to write internet addresses, missing out
+redundant parts or digts.. This procedure is the opposite of
+[cmd contract].
+
+[call [cmd ::ip::contract] [arg address]]
+
+Convert a [cmd normalize]d internet address into a more compact form
+suitable for displaying to users.
+
+[call [cmd ::ip::distance] [arg ipaddr1] [arg ipaddr2]]
+
+This command computes the (integer) distance from IPv4 address
+[arg ipaddr1] to IPv4 address [arg ipaddr2], i.e. "ipaddr2 - ipaddr1"
+
+[para]
+[example {
+ % ::ip::distance 1.1.1.1 1.1.1.5
+ 4
+}]
+
+[call [cmd ::ip::nextIp] [arg ipaddr] [opt [arg offset]]]
+
+This command adds the integer [arg offset] to the IPv4 address [arg ipaddr]
+and returns the new IPv4 address.
+
+[para]
+[example {
+ % ::ip::distance 1.1.1.1 4
+ 1.1.1.5
+}]
+
+[call [cmd ::ip::prefix] [arg address]]
+
+Returns the address prefix generated by masking the address part with
+the mask if provided. If there is no mask then it is equivalent to
+calling [cmd normalize]
+
+[call [cmd ::ip::type] [arg address]]
+
+[call [cmd ::ip::mask] [arg address]]
+
+If the address supplied includes a mask then this is returned
+otherwise returns an empty string.
+
+[call [cmd ::ip::prefixToNative] [arg prefix]]
+
+This command converts the string [arg prefix] from dotted form
+(<ipaddr>/<mask> format) to native (hex) form. Returns a list
+containing two elements, ipaddress and mask, in this order, in
+hexadecimal notation.
+
+[para]
+[example {
+ % ip::prefixToNative 1.1.1.0/24
+ 0x01010100 0xffffff00
+}]
+
+[call [cmd ::ip::nativeToPrefix] [arg nativeList]|[arg native] \
+ [opt [option -ipv4]]]
+
+This command converts from native (hex) form to dotted form.
+It is the complement of [cmd ::ip::prefixToNative].
+
+[para]
+[list_begin arguments]
+[arg_def list nativeList in]
+
+List of several ip addresses in native form. The native form is a list
+as returned by [cmd ::ip::prefixToNative].
+
+[arg_def list native in]
+
+A list as returned by [cmd ::ip::prefixToNative].
+
+[list_end]
+[para]
+
+The command returns a list of addresses in dotted form if it was
+called with a list of addresses. Otherwise a single address in dotted
+form is returned.
+
+[para]
+[example {
+ % ip::nativeToPrefix {0x01010100 0xffffff00} -ipv4
+ 1.1.1.0/24
+}]
+
+[call [cmd ::ip::intToString] [arg number] [opt [option -ipv4]]]
+
+This command converts from an ip address specified as integer number
+to dotted form.
+
+[para]
+[example {
+ ip::intToString 4294967295
+ 255.255.255.255
+}]
+
+[call [cmd ::ip::toInteger] [arg ipaddr]]
+
+This command converts a dotted form ip into an integer number.
+
+[para]
+[example {
+ % ::ip::toInteger 1.1.1.0
+ 16843008
+}]
+
+[call [cmd ::ip::toHex] [arg ipaddr]]
+
+This command converts dotted form ip into a hexadecimal number.
+
+[para]
+[example {
+ % ::ip::toHex 1.1.1.0
+ 0x01010100
+}]
+
+[call [cmd ::ip::maskToInt] [arg ipmask]]
+
+This command convert an ipmask in either dotted (255.255.255.0) form
+or mask length form (24) into an integer number.
+
+[para]
+[example {
+ ::ip::maskToInt 24
+ 4294967040
+}]
+
+[call [cmd ::ip::broadcastAddress] [arg prefix] [opt [option -ipv4]]]
+
+This commands returns a broadcast address in dotted form for the given
+route [arg prefix], either in the form "addr/mask", or in native
+form. The result is in dotted form.
+
+[para]
+[example {
+ ::ip::broadcastAddress 1.1.1.0/24
+ 1.1.1.255
+
+ ::ip::broadcastAddress {0x01010100 0xffffff00}
+ 0x010101ff
+}]
+
+[call [cmd ::ip::maskToLength] \
+ [arg dottedMask]|[arg integerMask]|[arg hexMask] \
+ [opt [option -ipv4]]]
+
+This command converts the dotted or integer form of an ipmask to
+the mask length form.
+
+[para]
+[example {
+ ::ip::maskToLength 0xffffff00 -ipv4
+ 24
+
+ % ::ip::maskToLength 255.255.255.0
+ 24
+}]
+
+[call [cmd ::ip::lengthToMask] [arg maskLength] \
+ [opt [option -ipv4]]]
+
+This command converts an ipmask in mask length form to its dotted
+form.
+
+[para]
+[example {
+ ::ip::lengthToMask 24
+ 255.255.255.0
+}]
+
+[call [cmd ::ip::nextNet] [arg ipaddr] [arg ipmask] \
+ [opt [arg count]] \
+ [opt [option -ipv4]]]
+
+This command returns an ipaddress in the same position in the
+[arg count] next network. The default value for [arg count] is
+[const 1].
+
+[para]
+
+The address can be specified as either integer number or in dotted
+form. The mask can be specified as either integer number, dotted form,
+or mask length form.
+
+[para]
+
+The result is in hex form.
+
+[call [cmd ::ip::isOverlap] [arg prefix] [arg prefix]...]
+
+This command checks if the given ip prefixes overlap. All arguments
+are in dotted "addr/mask" form. All arguments after the first prefix
+are compared against the first prefix. The result is a boolean
+value. It is true if an overlap was found for any of the prefixes.
+
+[para]
+[example {
+ % ::ip::isOverlap 1.1.1.0/24 2.1.0.1/32
+ 0
+
+ ::ip::isOverlap 1.1.1.0/24 2.1.0.1/32 1.1.1.1/32
+ 1
+}]
+
+[call [cmd ::ip::isOverlapNative] \
+ [opt [option -all]] \
+ [opt [option -inline]] \
+ [opt [option -ipv4]] \
+ [arg hexipaddr] [arg hexipmask] [arg hexiplist]]
+
+This command is similar to [cmd ::ip::isOverlap], however the
+arguments are in the native form, and the form of the result is under
+greater control of the caller.
+
+If the option [option -all] is specified it checks all addresses for
+overlap, not only until the first one is found.
+
+If the option [option -inline] is specified the command returns the
+overlapping prefix instead of index values.
+
+[para]
+
+The result of the command is, depending on the specified options,
+
+[list_begin definitions]
+[def {no options}]
+
+The index of the first overlap found, or 0 if there is none.
+
+[def -all]
+
+A list containing the indices of all overlaps found, or an empty list
+
+if there are none.
+
+[def -inline]
+
+The first overlapping prefix, or an empoty string if there is none.
+
+[def {-all -inline}]
+
+A list containing the prefixes of all overlaps found, or an empty list
+if there are none.
+
+[list_end]
+
+[para]
+[example {
+ % ::ip::isOverlapNative 0x01010100 0xffffff00 {{0x02010001 0xffffffff}}
+ 0
+
+ % ::ip::isOverlapNative 0x01010100 0xffffff00 {{0x02010001 0xffffffff} {0x01010101 0xffffffff}}
+ 2
+}]
+
+[call [cmd ::ip::ipToLayer2Multicast] [arg ipaddr]]
+
+This command an converts ipv4 address in dotted form into a layer 2
+multicast address, also in dotted form.
+
+[para]
+[example {
+ % ::ip::ipToLayer2Multicast 224.0.0.2
+ 01.00.5e.00.00.02
+}]
+
+[call [cmd ::ip::ipHostFromPrefix] [arg prefix] \
+ [opt "[option -exclude] [arg prefixExcludeList]"]]
+
+This command returns a host address from a prefix in the form
+"ipaddr/masklen", also making sure that the result is not an address
+found in the [arg prefixExcludeList].
+
+The result is an ip address in dotted form.
+
+[para]
+[example {
+ %::ip::ipHostFromPrefix 1.1.1.5/24
+ 1.1.1.1
+
+ %::ip::ipHostFromPrefix 1.1.1.1/32
+ 1.1.1.1
+}]
+
+[call [cmd ::ip::reduceToAggregates] [arg prefixlist]]
+
+This command finds nets that overlap and filters out the more specific
+nets. The prefixes are in either addr/mask form or in native format.
+
+The result is a list containing the non-overlapping ip prefixes from
+the input.
+
+[para]
+[example {
+ % ::ip::reduceToAggregates {1.1.1.0/24 1.1.0.0/8 2.1.1.0/24 1.1.1.1/32 }
+ 1.0.0.0/8 2.1.1.0/24
+}]
+
+[call [cmd ::ip::longestPrefixMatch] [arg ipaddr] [arg prefixlist] \
+ [opt [option -ipv4]]]
+
+This command finds longest prefix match from set of prefixes, given a
+specific host address. The prefixes in the list are in either native
+or dotted form, whereas the host address is in either ipprefix format,
+dotted form, or integer form.
+
+The result is the prefix which is the most specific match to the host
+address.
+
+[para]
+[example {
+ % ::ip::longestPrefixMatch 1.1.1.1 {1.1.1.0/24 1.0.0.0/8 2.1.1.0/24 1.1.1.0/28 }
+ 1.1.1.0/28
+}]
+
+[call [cmd ::ip::collapse] [arg prefixlist]]
+
+This commands takes a list of prefixes and returns a list prefixes
+with the largest possible subnet masks covering the input, in this
+manner collapsing adjacent prefixes into larger ranges.
+
+[para] This is different from [cmd ::ip::reduceToAggregates] in that
+the latter only removes specific nets from a list when they are
+covered by other elements of the input whereas this command actively
+merges nets into larger ranges when they are adjacent to each other.
+
+[para]
+[example {
+% ::ip::collapse {1.2.2.0/24 1.2.3.0/24}
+1.2.2.0/23
+}]
+
+[call [cmd ::ip::subtract] [arg prefixlist]]
+
+This command takes a list of prefixes, some of which are prefixed by a
+dash. These latter [term negative] prefixes are used to punch holes
+into the ranges described by the other, [term positive],
+prefixes. I.e. the negative prefixes are subtracted frrom the positive
+ones, resulting in a larger list of describes describing the covered
+ranges only as positives.
+
+[list_end]
+
+[comment { ----------------------------------------------------------- }]
+
+[section EXAMPLES]
+[para]
+
+[example {
+% ip::version ::1
+6
+% ip::version 127.0.0.1
+4
+}]
+
+[example {
+% ip::normalize 127/8
+127.0.0.0/8
+% ip::contract 192.168.0.0
+192.168
+%
+% ip::normalize fec0::1
+fec0:0000:0000:0000:0000:0000:0000:0001
+% ip::contract fec0:0000:0000:0000:0000:0000:0000:0001
+fec0::1
+}]
+
+[example {
+% ip::equal 192.168.0.4/16 192.168.0.0/16
+1
+% ip::equal fec0::1/10 fec0::fe01/10
+1
+}]
+
+[comment { ----------------------------------------------------------- }]
+
+[section {REFERENCES}]
+
+[list_begin enumerated]
+
+[enum]
+ Postel, J. "Internet Protocol." RFC 791, September 1981,
+ ([uri http://www.ietf.org/rfc/rfc791.txt])
+
+[enum]
+ Hinden, R. and Deering, S.,
+ "Internet Protocol Version 6 (IPv6) Addressing Architecture",
+ RFC 3513, April 2003
+ ([uri http://www.ietf.org/rfc/rfc3513.txt])
+
+[list_end]
+
+[section AUTHORS]
+Pat Thoyts
+
+[vset CATEGORY dns]
+[include ../doctools2base/include/feedback.inc]
+[manpage_end]