summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lisplib.c3
-rw-r--r--share/txr/stdlib/socket.tl114
-rw-r--r--tests/014/in6addr-str.expected0
-rw-r--r--tests/014/in6addr-str.tl120
-rw-r--r--tests/014/inaddr-str.expected0
-rw-r--r--tests/014/inaddr-str.tl78
-rw-r--r--txr.1122
7 files changed, 435 insertions, 2 deletions
diff --git a/lisplib.c b/lisplib.c
index 3478cd1c..6999efcd 100644
--- a/lisplib.c
+++ b/lisplib.c
@@ -343,6 +343,7 @@ static val sock_set_entries(val dlt, val fun)
lit("ai-numericserv"),
lit("str-inaddr"), lit("str-in6addr"),
lit("str-inaddr-net"), lit("str-in6addr-net"),
+ lit("inaddr-str"), lit("in6addr-str"),
lit("shut-rd"), lit("shut-wr"), lit("shut-rdwr"),
lit("open-socket"), lit("open-socket-pair"),
lit("sock-bind"), lit("sock-connect"), lit("sock-listen"),
@@ -352,7 +353,7 @@ static val sock_set_entries(val dlt, val fun)
};
val name_noload[] = {
lit("family"), lit("addr"), lit("port"), lit("flow-info"),
- lit("scope-id"), lit("path"), lit("flags"), lit("socktype"),
+ lit("scope-id"), lit("prefix"), lit("path"), lit("flags"), lit("socktype"),
lit("protocol"), lit("canonname"), nil
};
set_dlt_entries(dlt, name, fun);
diff --git a/share/txr/stdlib/socket.tl b/share/txr/stdlib/socket.tl
index 3236460c..54df9758 100644
--- a/share/txr/stdlib/socket.tl
+++ b/share/txr/stdlib/socket.tl
@@ -29,10 +29,12 @@
(defstruct sockaddr-in sockaddr
(addr 0) (port 0)
+ (prefix 32)
(:static family af-inet))
(defstruct sockaddr-in6 sockaddr
(addr 0) (port 0) (flow-info 0) (scope-id 0)
+ (prefix 128)
(:static family af-inet6))
(defstruct sockaddr-un sockaddr
@@ -152,6 +154,118 @@
(prefix (if (search cand-prefix '(0 0)) pieces cand-prefix)))
`@(sys:in6addr-condensed-text prefix)/@(or width w)`))))
+(defun inaddr-str (str)
+ (labels ((invalid ()
+ (error "~s: invalid address ~s" 'inaddr-str str))
+ (mkaddr (octets port)
+ (unless [all octets (op <= 0 @1 255)]
+ (invalid))
+ (unless (<= 0 port 65535)
+ (invalid))
+ (new sockaddr-in
+ addr (+ (ash (pop octets) 24)
+ (ash (pop octets) 16)
+ (ash (pop octets) 8)
+ (car octets))
+ port port))
+ (mkaddr-pf (octets prefix port)
+ (unless [all octets (op <= 0 @1 255)]
+ (invalid))
+ (unless (<= 0 prefix 32)
+ (invalid))
+ (unless (<= 0 port 65535)
+ (invalid))
+ (let* ((addr (+ (ash (or (pop octets) 0) 24)
+ (ash (or (pop octets) 0) 16)
+ (ash (or (pop octets) 0) 8)
+ (or (car octets) 0))))
+ (new sockaddr-in
+ addr (logand addr (ash -1 (- 32 prefix)))
+ port port
+ prefix prefix))))
+ (cond
+ ((r^$ #/\d+\.\d+\.\d+\.\d+:\d+/ str)
+ (tree-bind (addr port) (split* str (rpos #\: str))
+ (mkaddr [mapcar toint (spl #\. addr)] (toint port))))
+ ((r^$ #/\d+\.\d+\.\d+\.\d+(:\d+)?/ str)
+ (mkaddr [mapcar toint (spl #\. str)] 0))
+ ((r^$ #/\d+(\.\d+(\.\d+(\.\d+)?)?)?\/\d+/ str)
+ (tree-bind (addr prefix) (spl #\/ str)
+ (mkaddr-pf [mapcar toint (spl #\. addr)] (toint prefix) 0)))
+ ((r^$ #/\d+(\.\d+(\.\d+(\.\d+)?)?)?\/\d+:\d+/ str)
+ (tree-bind (addr prefix port) (split-str-set str ":/")
+ (mkaddr-pf [mapcar toint (spl #\. addr)] (toint prefix) (toint port))))
+ (t (invalid)))))
+
+(defun in6addr-str (str)
+ (labels ((invalid ()
+ (error "~s: invalid address ~s" 'in6addr-str str))
+ (mkaddr-full (pieces)
+ (unless [all pieces (op <= 0 @1 #xffff)]
+ (invalid))
+ (unless (eql 8 (length pieces))
+ (invalid))
+ (new sockaddr-in6
+ addr (reduce-left (op + @2 (ash @1 16)) pieces)))
+ (mkaddr-brev (pieces-x pieces-y)
+ (let ((len-x (len pieces-x))
+ (len-y (len pieces-y)))
+ (unless (<= (+ len-x len-y) 7)
+ (invalid))
+ (let* ((val-x (reduce-left (op + @2 (ash @1 16)) pieces-x 0))
+ (val-y (reduce-left (op + @2 (ash @1 16)) pieces-y 0))
+ (addr (cond
+ ((null pieces-x) val-y)
+ ((null pieces-y) (ash val-x (* 16 (- 8 len-x))))
+ (t (+ val-y
+ (ash val-x (* 16 (- 8 len-x))))))))
+ (new sockaddr-in6
+ addr addr))))
+ (str-to-pieces (str)
+ (unless (empty str)
+ [mapcar (lop toint 16) (spl #\: str)]))
+ (octets-to-pieces (octets)
+ (unless [all octets (op <= 0 @1 255)]
+ (invalid))
+ (list (+ (ash (pop octets) 8)
+ (pop octets))
+ (+ (ash (pop octets) 8)
+ (pop octets)))))
+ (cond
+ ((r^$ #/\[.*\]:\d+/ str)
+ (tree-bind (addr-str port-str) (split* str (rpos #\: str))
+ (let ((addr (in6addr-str [addr-str 1..-1]))
+ (port (toint port-str)))
+ (unless (<= 0 port 65535)
+ (invalid))
+ (set addr.port port)
+ addr)))
+ ((r^$ #/[^\/]+\/\d+/ str)
+ (tree-bind (addr-str prefix-str) (split* str (rpos #\/ str))
+ (let ((addr (in6addr-str addr-str))
+ (prefix (toint prefix-str)))
+ (unless (<= 0 prefix 128)
+ (invalid))
+ (upd addr.addr (logand (ash -1 (- 128 prefix))))
+ (set addr.prefix prefix)
+ addr)))
+ ((r^$ #/[\da-fA-F]*(:[\da-fA-F]*)*/ str)
+ (upd str (regsub #/::/ "@"))
+ (let* ((str-splat (regsub #/::/ "@" str))
+ (maj-pieces (spl #\@ str-splat)))
+ (caseql (len maj-pieces)
+ (1 (mkaddr-full (str-to-pieces (car maj-pieces))))
+ (2 (mkaddr-brev (str-to-pieces (car maj-pieces))
+ (str-to-pieces (cadr maj-pieces))))
+ (t (invalid)))))
+ ((r^$ #/::0*[fF][fF][fF][fF]:\d+\.\d+\.\d+\.\d+/ str)
+ (let* ((bigsplit (split* str (rpos #\: str)))
+ (4part (cadr bigsplit))
+ (octets [mapcar toint (spl #\. 4part)])
+ (pieces (cons #xffff (octets-to-pieces octets))))
+ (mkaddr-brev nil pieces)))
+ (t (invalid)))))
+
(defplace (sock-peer sock) body
(getter setter
^(macrolet ((,getter () ^(sock-peer ',',sock))
diff --git a/tests/014/in6addr-str.expected b/tests/014/in6addr-str.expected
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/tests/014/in6addr-str.expected
diff --git a/tests/014/in6addr-str.tl b/tests/014/in6addr-str.tl
new file mode 100644
index 00000000..a85da416
--- /dev/null
+++ b/tests/014/in6addr-str.tl
@@ -0,0 +1,120 @@
+(load "../common.tl")
+
+(test (in6addr-str "junk") :error)
+
+(test (in6addr-str "0:0:0:0:0:0:0:0:0") :error)
+(test (in6addr-str "0:0:0:0:0:0") :error)
+(test (in6addr-str "0:0:0:0") :error)
+(test (in6addr-str "") :error)
+
+(test (in6addr-str "0:0:0:0:0:0:0:x:0") :error)
+
+(test (in6addr-str ":0:0:0:0:0:0:0:0:0") :error)
+(test (in6addr-str "0:0:0:0:0:0:0:0:0:") :error)
+
+(test (in6addr-str "0:0:0:0:0:0:0:0:FFFFF") :error)
+(test (in6addr-str "0:0:0:0:0:0:0:FFFFF:0") :error)
+(test (in6addr-str "0:0:0:0:0:0:FFFFF:0:0") :error)
+(test (in6addr-str "0:0:0:0:0:FFFFF:0:0:0") :error)
+(test (in6addr-str "0:0:0:0:FFFFF:0:0:0:0") :error)
+(test (in6addr-str "0:0:0:FFFFF:0:0:0:0:0") :error)
+(test (in6addr-str "0:0:FFFFF:0:0:0:0:0:0") :error)
+(test (in6addr-str "0:FFFFF:0:0:0:0:0:0:0") :error)
+(test (in6addr-str "FFFFF:0:0:0:0:0:0:0:0") :error)
+
+(test (in6addr-str "0:0:0:0:0:0:0:0/") :error)
+(test (in6addr-str "0:0:0:0:0:0:0:0/129") :error)
+(test (in6addr-str "[0:0:0:0:0:0:0:0]:") :error)
+(test (in6addr-str "[0:0:0:0:0:0:0:0]:65536") :error)
+
+(test (in6addr-str "0:0:0:0:0:0:0:0")
+ #S(sockaddr-in6 addr 0
+ port 0 flow-info 0 scope-id 0 prefix 128))
+(test (in6addr-str "1111:2222:3333:4444:5555:6666:7777:8888")
+ #S(sockaddr-in6 addr 22685837286468424649968941046919825544
+ port 0 flow-info 0 scope-id 0 prefix 128))
+(test (in6addr-str "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF")
+ #S(sockaddr-in6 addr 340282366920938463463374607431768211455
+ port 0 flow-info 0 scope-id 0 prefix 128))
+
+(test (in6addr-str "[0:0:0:0:0:0:0:0]:42")
+ #S(sockaddr-in6 addr 0
+ port 42 flow-info 0 scope-id 0 prefix 128))
+(test (in6addr-str "[1111:2222:3333:4444:5555:6666:7777:8888]:42")
+ #S(sockaddr-in6 addr 22685837286468424649968941046919825544
+ port 42 flow-info 0 scope-id 0 prefix 128))
+(test (in6addr-str "[FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF]:42")
+ #S(sockaddr-in6 addr 340282366920938463463374607431768211455
+ port 42 flow-info 0 scope-id 0 prefix 128))
+
+(test (in6addr-str "0:0:0:0:0:0:0:0/16")
+ #S(sockaddr-in6 addr 0
+ port 0 flow-info 0 scope-id 0 prefix 16))
+(test (in6addr-str "1111:2222:3333:4444:5555:6666:7777:8888/16")
+ #S(sockaddr-in6 addr 22685144974938661909049738462362599424
+ port 0 flow-info 0 scope-id 0 prefix 16))
+(test (in6addr-str "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF/16")
+ #S(sockaddr-in6 addr 340277174624079928635746076935438991360
+ port 0 flow-info 0 scope-id 0 prefix 16))
+(test (in6addr-str "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/16")
+ #S(sockaddr-in6 addr 340277174624079928635746076935438991360
+ port 0 flow-info 0 scope-id 0 prefix 16))
+
+(test (in6addr-str "[0:0:0:0:0:0:0:0/16]:42")
+ #S(sockaddr-in6 addr 0
+ port 42 flow-info 0 scope-id 0 prefix 16))
+(test (in6addr-str "[1111:2222:3333:4444:5555:6666:7777:8888/16]:42")
+ #S(sockaddr-in6 addr 22685144974938661909049738462362599424
+ port 42 flow-info 0 scope-id 0 prefix 16))
+(test (in6addr-str "[FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF/16]:42")
+ #S(sockaddr-in6 addr 340277174624079928635746076935438991360
+ port 42 flow-info 0 scope-id 0 prefix 16))
+
+(test (in6addr-str "1:2:3:4:5:6::7:8:9") :error)
+(test (in6addr-str "1:2:3:4:5:6::7:8:9") :error)
+(test (in6addr-str "1:2:3:4:5:6::7:8") :error)
+
+(test (in6addr-str "::1")
+ #S(sockaddr-in6 addr 1
+ port 0 flow-info 0 scope-id 0 prefix 128))
+
+(test (in6addr-str "1::")
+ #S(sockaddr-in6 addr 5192296858534827628530496329220096
+ port 0 flow-info 0 scope-id 0 prefix 128))
+
+(test (in6addr-str "1::1")
+ #S(sockaddr-in6 addr 5192296858534827628530496329220097
+ port 0 flow-info 0 scope-id 0 prefix 128))
+
+(test (in6addr-str "1:2::3:4")
+ #S(sockaddr-in6 addr 5192455314859856157205683417317380
+ port 0 flow-info 0 scope-id 0 prefix 128))
+
+(test (in6addr-str "::ffff:1.2.3.4.5") :error)
+(test (in6addr-str "::ffff:1.2.3.4:5") :error)
+(test (in6addr-str "::ffff:1.2.3") :error)
+(test (in6addr-str "::ffff:1.2.3:4") :error)
+
+(test (in6addr-str "::ffff:1.2.3.4")
+ #S(sockaddr-in6 addr 281470698652420
+ port 0 flow-info 0 scope-id 0 prefix 128))
+
+(test (in6addr-str "::FFFF:1.2.3.4")
+ #S(sockaddr-in6 addr 281470698652420
+ port 0 flow-info 0 scope-id 0 prefix 128))
+
+(test (in6addr-str "::FfFf:1.2.3.4")
+ #S(sockaddr-in6 addr 281470698652420
+ port 0 flow-info 0 scope-id 0 prefix 128))
+
+(test (in6addr-str "::FFFF:1.2.3.4/96")
+ #S(sockaddr-in6 addr 281470681743360
+ port 0 flow-info 0 scope-id 0 prefix 96))
+
+(test (in6addr-str "[::ffff:1.2.3.4]:42")
+ #S(sockaddr-in6 addr 281470698652420
+ port 42 flow-info 0 scope-id 0 prefix 128))
+
+(test (in6addr-str "[::FFFF:1.2.3.4/96]:42")
+ #S(sockaddr-in6 addr 281470681743360
+ port 42 flow-info 0 scope-id 0 prefix 96))
diff --git a/tests/014/inaddr-str.expected b/tests/014/inaddr-str.expected
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/tests/014/inaddr-str.expected
diff --git a/tests/014/inaddr-str.tl b/tests/014/inaddr-str.tl
new file mode 100644
index 00000000..67283188
--- /dev/null
+++ b/tests/014/inaddr-str.tl
@@ -0,0 +1,78 @@
+(load "../common.tl")
+
+(test (inaddr-str "junk") :error)
+(test (inaddr-str "0.0.0.0.0") :error)
+(test (inaddr-str "0.0.0") :error)
+(test (inaddr-str "0") :error)
+(test (inaddr-str "") :error)
+(test (inaddr-str "0.0.0.nnn") :error)
+(test (inaddr-str "0.0.0.256") :error)
+(test (inaddr-str "0.0.256.0") :error)
+(test (inaddr-str "0.256.0.0") :error)
+(test (inaddr-str "256.0.0.0") :error)
+(test (inaddr-str "0.0.0.0:65537") :error)
+(test (inaddr-str "0.0.0.0/33") :error)
+(test (inaddr-str "0.0.0.0/32:65537") :error)
+(test (inaddr-str "0.0.0.0/33:0") :error)
+(test (inaddr-str "0.0.0.0:0/0") :error)
+(test (inaddr-str "0.0.0.") :error)
+(test (inaddr-str "0.0..0") :error)
+(test (inaddr-str "0..0.0") :error)
+(test (inaddr-str ".0.0.0") :error)
+(test (inaddr-str "0.0.0.0:") :error)
+
+(test (inaddr-str "0.0.0.0")
+ #S(sockaddr-in addr 0 port 0 prefix 32))
+(test (inaddr-str "1.2.3.4")
+ #S(sockaddr-in addr 16909060 port 0 prefix 32))
+(test (inaddr-str "255.255.255.255")
+ #S(sockaddr-in addr 4294967295 port 0 prefix 32))
+
+(test (inaddr-str "0.0.0.0:0")
+ #S(sockaddr-in addr 0 port 0 prefix 32))
+(test (inaddr-str "1.2.3.4:5")
+ #S(sockaddr-in addr 16909060 port 5 prefix 32))
+(test (inaddr-str "255.255.255.255:65535")
+ #S(sockaddr-in addr 4294967295 port 65535 prefix 32))
+
+(test (inaddr-str "0.0.0.0/0")
+ #S(sockaddr-in addr 0 port 0 prefix 0))
+(test (inaddr-str "1.2.3.4/8")
+ #S(sockaddr-in addr 16777216 port 0 prefix 8))
+(test (inaddr-str "255.255.255.255/24")
+ #S(sockaddr-in addr 4294967040 port 0 prefix 24))
+
+(test (inaddr-str "0.0.0/0")
+ #S(sockaddr-in addr 0 port 0 prefix 0))
+(test (inaddr-str "0.0/0")
+ #S(sockaddr-in addr 0 port 0 prefix 0))
+(test (inaddr-str "0/0")
+ #S(sockaddr-in addr 0 port 0 prefix 0))
+
+(test (inaddr-str "1.2.3/8")
+ #S(sockaddr-in addr 16777216 port 0 prefix 8))
+(test (inaddr-str "1.2/8")
+ #S(sockaddr-in addr 16777216 port 0 prefix 8))
+(test (inaddr-str "1/8")
+ #S(sockaddr-in addr 16777216 port 0 prefix 8))
+
+(test (inaddr-str "0.0.0.0/0:1234")
+ #S(sockaddr-in addr 0 port 1234 prefix 0))
+(test (inaddr-str "1.2.3.4/8:1234")
+ #S(sockaddr-in addr 16777216 port 1234 prefix 8))
+(test (inaddr-str "255.255.255.255/24:1234")
+ #S(sockaddr-in addr 4294967040 port 1234 prefix 24))
+
+(test (inaddr-str "0.0.0/0:1234")
+ #S(sockaddr-in addr 0 port 1234 prefix 0))
+(test (inaddr-str "0.0/0:1234")
+ #S(sockaddr-in addr 0 port 1234 prefix 0))
+(test (inaddr-str "0/0:1234")
+ #S(sockaddr-in addr 0 port 1234 prefix 0))
+
+(test (inaddr-str "1.2.3/8:1234")
+ #S(sockaddr-in addr 16777216 port 1234 prefix 8))
+(test (inaddr-str "1.2/8:1234")
+ #S(sockaddr-in addr 16777216 port 1234 prefix 8))
+(test (inaddr-str "1/8:1234")
+ #S(sockaddr-in addr 16777216 port 1234 prefix 8))
diff --git a/txr.1 b/txr.1
index 23d149af..300498f0 100644
--- a/txr.1
+++ b/txr.1
@@ -63412,7 +63412,7 @@ which is static, and initialized to
.coNP Structure @ sockaddr-in
.synb
.mets (defstruct sockaddr-in sockaddr
-.mets \ \ (addr 0) (port 0)
+.mets \ \ (addr 0) (port 0) (prefix 32)
.mets \ \ (:static family af-inet))
.syne
.desc
@@ -63446,6 +63446,12 @@ function is used with the aim of looking up the address of a host, without
caring about the port number.
The
+.code prefix
+field is set by the function
+.codn inaddr-str ,
+when it recognizes and parses a prefix field in the textual representation.
+
+The
.code family
static slot holds the value
.codn af-inet .
@@ -63454,6 +63460,7 @@ static slot holds the value
.synb
.mets (defstruct sockaddr-in6 sockaddr
.mets \ \ (addr 0) (port 0) (flow-info 0) (scope-id 0)
+.mets \ \ (prefix 128)
.mets \ \ (:static family af-inet6))
.syne
.desc
@@ -63488,6 +63495,12 @@ slots of the
C language structure. Their meaning and use are beyond the scope of this document.
The
+.code prefix
+field is set by the function
+.codn in6addr-str ,
+when it recognizes and parses a prefix field in the textual representation.
+
+The
.code family
static slot holds the value
.codn af-inet6 .
@@ -64161,6 +64174,113 @@ excluding the contiguous all-zero bits in the least significant position:
how many times the address can be shifted to the right before a 1 appears
in the least significant bit.
+.coNP Functions @ inaddr-str and @ in6addr-str
+.synb
+.mets (inaddr-str << string )
+.mets (in6addr-str << string )
+.syne
+.desc
+The
+.code inaddr-str
+and
+.code in6addr-str
+functions recover an IPv4 or IPv6 address from a textual representation.
+If the parse is successful, the address is returned as, respectively, a
+.code sockaddr-in
+or
+.code sockaddr-in6
+structure.
+
+If
+.meta string
+is a malformed address, due to any issue such as invalid syntax or
+a numeric value being out of range, an exception is thrown.
+
+The
+.code inaddr-str
+function recognizes the dot notation consisting of four decimal numbers
+separated by period characters. The numbers must be in the range 0 to 255.
+Note: superfluous leading zeros are permitted, though this is a nonstandard
+extension; not all implementations of this notations support this.
+
+A prefix may be specified in the notation as a slash followed by a decimal
+number, in the range 0 to 32. In this case, the integer value of the
+prefix appears as the
+.code prefix
+member of the returned
+.code sockaddr-in
+structure. Furthermore, the address is masked, so that any bits not
+included in the prefix are zero. For instance, the address
+.str 255.255.255.255/1
+is equivalent to
+.strn 128.0.0.0 ,
+except that the
+.code prefix
+if the returned structure is 1 rather than 32.
+When a prefix is not specified, the
+.code prefix
+member of the structure retains its default value of 32.
+When the prefix is specified, the address part need not contain all four
+octets; it may contain between one and four octets. Thus,
+.str 192.168/16
+is a valid address, equivalent to
+.strn 192.168.0.0/16 .
+
+A port number may be specified in the notation as a colon, followed by a
+decimal number in the range 0 to 65535. The integer value of this port
+number appears as the
+.code port
+member of the returned structure. An example of this notation is
+.strn 127.0.0.1:23 .
+
+A prefix and port number may both be specified; in this case the prefix must
+appear first, followed by the port number. For example,
+.strn "127/8:23" .
+
+The
+.code in6addr-str
+function recognizes the IPv6 notation consisting of 16-bit hexadecimal pieces
+separated by colons. If the operation is successful, it returns a
+.code sockaddr-in6
+structure. Each piece must be a value in the range 0 to FFFF.
+The hexadecimal digits may be any mixture of upper and lower case. Leading
+zeros are permitted.
+Up to eight such pieces must be specified. If fewer pieces are specified,
+then the token
+.code ::
+(double colon)
+must appear in the address exactly once. That token denotes the condensation of
+a sufficient number of zero-valued pieces to make eight pieces.
+The token must be in one of three positions: it may be the leftmost element of
+the address, immediately followed by a hexadecimal piece; it may be the rightmost element
+of the address, preceded by a hexadecimal piece; or else, it may be in the
+middle of the address, flanked on both sides by hexadecimal pieces.
+
+The
+.code in6addr-str
+also recognizes the special notation for IPv6-mapped IPv4 addresses. This
+notation consists of the address string
+.str ::FFFF
+which may appear in any upper/lower case mixture, possibly with leading
+zeros, followed by an IPv4 address given in the four-octet dot notation.
+For example,
+.strn ::FFFF:127.0.0.1 .
+
+A prefix may be specified using a slash, followed by a decimal number in the
+range 0 to 128. The handling of the prefix is similar to that of
+.code inaddr-str
+except that pieces of the address may not be omitted. Condensing the
+pieces of the IPv6 address is always done by means of the
+.code ::
+token, whether or not a prefix is present. Furthermore, the octets specified in
+the IPv6-mapped IPv4 notation must all be present, regardless of the prefix.
+
+A port number may be specified in the notation as follows: the entire address,
+including any slash-separated prefix, must appear surrounded in square
+brackets. The closing square bracket must be followed by a colon and one or
+more digits denoting a decimal number in the range 0 to 65535. For instance
+.strn "[1:2:3::4/64]:1234".
+
.SS* Unix Terminal Control
\*(TX provides access to the terminal control "termios" interfaces defined by