+ (or (ipmask-cidl-slash (ipnet-width ipn) mask)
+ (ipaddr-string (make-instance (class-of net) :addr mask))))))
+
+(defmethod print-object ((ipn ipnet) stream)
+ (print-unreadable-object (ipn stream :type t)
+ (write-string (ipnet-string ipn) stream)))
+
+(defun parse-subnet (class width max str &key (start 0) (end nil) (slashp t))
+ "Parse a subnet description from (a substring of) STR.
+
+ Suppose we have a parent network, with a prefix length of MAX. The WIDTH
+ gives the overall length of addresses of the appropriate class, i.e.,
+ (ipaddr-width WIDTH), but in fact callers have already computed this for
+ their own reasons.
+
+ Parse (the designated substring of) STR to construct the base address of a
+ subnet. The string should have the form BASE/MASK, where the MASK is
+ either a literal bitmask (in the usual syntax for addresses) or an integer
+ prefix length. An explicit prefix length is expected to cover the entire
+ address including the parent prefix: an error is signalled if the prefix
+ isn't long enough to cover any of the subnet. A mask is parsed relative
+ to the end of the parent address, just as the subnet base address is.
+
+ Returns the relative base address and mask as two integer values."
+
+ (setf-default end (length str))
+ (let ((sl (and slashp (position #\/ str :start start :end end))))
+ (multiple-value-bind (addr lo hi)
+ (parse-partial-ipaddr class str :max max
+ :start start :end (or sl end))
+ (let* ((present (integer-netmask hi (- hi lo)))
+ (mask (cond ((not sl)
+ present)
+ ((every #'digit-char-p (subseq str (1+ sl) end))
+ (let ((length (parse-integer str
+ :start (1+ sl)
+ :end end)))
+ (unless (>= length (- width max))
+ (error "Mask doesn't reach subnet boundary"))
+ (integer-netmask max (- length (- width max)))))
+ (t
+ (parse-partial-ipaddr class str :max max
+ :start (1+ sl) :end end)))))
+ (unless (zerop (logandc2 mask present))
+ (error "Mask selects bits not present in base address"))
+ (values addr mask)))))
+
+(defun check-subipnet (base-ipn sub-addr sub-mask)
+ "Verify that SUB-NET/SUB-MASK is an appropriate subnet of BASE-IPN.
+
+ The BASE-IPN is an `ipnet'; SUB-ADDR and SUB-MASK are plain integers. If
+ the subnet is invalid (i.e., the subnet disagrees with its putative parent
+ over some of the fixed address bits) then an error is signalled; otherwise
+ return the combined base address (as an `ipaddr') and mask (as an
+ integer)."
+ (with-ipnet (base-net base-addr base-mask) base-ipn
+ (let* ((common (logand base-mask sub-mask))
+ (base-overlap (logand base-addr common))
+ (sub-overlap (logand sub-addr common))
+ (full-mask (logior base-mask sub-mask)))
+ (unless (or (zerop sub-overlap) (= sub-overlap base-overlap))
+ (error "Subnet doesn't match base network"))
+ (values (integer-ipaddr (logand full-mask (logior base-addr sub-addr))
+ base-net)
+ full-mask))))
+
+(export 'string-ipnet)
+(defun string-ipnet (str &key (start 0) (end nil))
+ "Parse an IP network description from the string STR.
+
+ A network description has the form ADDRESS/MASK, where the ADDRESS is a
+ base address in numeric form, and the MASK is either a netmask in the same
+ form, or an integer prefix length."
+ (setf str (stringify str))
+ (setf-default end (length str))
+ (let ((addr-class (guess-address-class str :start start :end end)))
+ (multiple-value-bind (addr mask)
+ (let ((width (ipaddr-width addr-class)))
+ (parse-subnet addr-class width width str
+ :start start :end end))
+ (make-ipnet (make-instance addr-class :addr addr)
+ (make-instance addr-class :addr mask)))))
+
+(defun parse-subipnet (ipn str &key (start 0) (end nil) (slashp t))
+ "Parse STR as a subnet of IPN.
+
+ This is mostly a convenience interface over `parse-subnet'; we compute
+ various of the parameters from IPN rather than requiring them to be passed
+ in explicitly.
+
+ Returns two values: the combined base address, as an `ipnaddr' and
+ combined mask, as an integer."
+
+ (let* ((addr-class (extract-class-name (ipnet-net ipn)))
+ (width (ipaddr-width addr-class))
+ (max (- width
+ (or (ipmask-cidl-slash width (ipnet-mask ipn))
+ (error "Base network has complex netmask")))))
+ (multiple-value-bind (addr mask)
+ (parse-subnet addr-class width max (stringify str)
+ :start start :end end :slashp slashp)
+ (check-subipnet ipn addr mask))))
+
+(export 'string-subipnet)
+(defun string-subipnet (ipn str &key (start 0) (end nil))
+ "Parse an IP subnet from a parent net IPN and a suffix string STR.
+
+ The (substring of) STR is expected to have the form ADDRESS/MASK, where
+ ADDRESS is a relative subnet base address, and MASK is either a relative
+ subnet mask or a (full) prefix length. Returns the resulting ipnet. If
+ the relative base address overlaps with the existing subnet (because the
+ base network's prefix length doesn't cover a whole number of components),
+ then the subnet base must either agree in the overlapping portion with the
+ parent base address or be zero.
+
+ For example, if IPN is the network 172.29.0.0/16, then `199/24' or
+ `199/255' both designate the subnet 172.29.199.0/24. Similarly, starting
+ from 2001:ba8:1d9:8000::/52, then `8042/ffff' and `42/64' both designate
+ the network 2001:ba8:1d9:8042::/64."
+
+ (multiple-value-bind (addr mask)
+ (parse-subipnet ipn str :start start :end end)
+ (ipaddr-ipnet addr mask)))
+
+(defun ipnet (net)
+ "Construct an IP-network object from the given argument.
+
+ A number of forms are acceptable:
+
+ * ADDR -- a single address, equivalent to (ADDR . N).
+ * (NET . MASK|nil) -- a single-object representation.
+ * IPNET -- return an equivalent (`equal', not necessarily `eql')
+ version."
+ (typecase net
+ (ipnet net)
+ ((or string symbol) (string-ipnet net))
+ (t (apply #'make-ipnet (pairify net nil)))))