From 8fe87aafd6aa77f5d4c1ac126e2d2e7e69ce236c Mon Sep 17 00:00:00 2001 From: "Peder O. Klingenberg" Date: Sun, 7 May 2017 17:58:41 +0200 Subject: [PATCH] Command to convert IPv6 addresses in zone files * lisp/textmodes/dns-mode.el (dns-mode-ipv6-to-nibbles): New command to convert IPv6 addresses to a format suitable for reverse lookup zone files. (dns-mode-reverse-and-expand-ipv6): The algorithm central to the command above. (dns-mode-map, dns-mode-menu): Key and menu item for the new command. * test/lisp/textmodes/dns-mode-tests.el (dns-mode-ipv6-conversion) (dns-mode-ipv6-text-replacement): Tests for new functionality in dns-mode.el (bug#26820) --- etc/NEWS | 8 +++ lisp/textmodes/dns-mode.el | 102 +++++++++++++++++++++++++++++++++- test/lisp/textmodes/dns-mode-tests.el | 58 +++++++++++++++++++ 3 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 test/lisp/textmodes/dns-mode-tests.el diff --git a/etc/NEWS b/etc/NEWS index 4599efd7da..293ef4f960 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -784,6 +784,14 @@ file. *** Emacs does no longer prompt the user before killing Flymake processes on exit. +** DNS mode + ++++ +*** DNS mode has a tool to convert IPv6 addresses +Reverse zones for IPv6 needs IPv6 addresses spelled out in a +cumbersome format. A new command 'dns-mode-ipv6-to-nibbles' helps +with the conversion. + * New Modes and Packages in Emacs 26.1 diff --git a/lisp/textmodes/dns-mode.el b/lisp/textmodes/dns-mode.el index 01f509d913..6f2f40cd2c 100644 --- a/lisp/textmodes/dns-mode.el +++ b/lisp/textmodes/dns-mode.el @@ -113,6 +113,7 @@ dns-mode-syntax-table (defvar dns-mode-map (let ((map (make-sparse-keymap))) (define-key map "\C-c\C-s" 'dns-mode-soa-increment-serial) + (define-key map "\C-c\C-e" 'dns-mode-ipv6-to-nibbles) map) "Keymap for DNS master file mode.") @@ -124,7 +125,8 @@ dns-mode-menu (easy-menu-define dns-mode-menu dns-mode-map "DNS Menu." '("DNS" - ["Increment SOA serial" dns-mode-soa-increment-serial t])) + ["Increment SOA serial" dns-mode-soa-increment-serial t] + ["Convert IPv6 address to nibbles" dns-mode-ipv6-to-nibbles t])) ;; Mode. @@ -220,6 +222,104 @@ dns-mode-soa-maybe-increment-serial ;; We return nil in case this is used in write-contents-functions. nil))) +;;;###autoload +(defun dns-mode-ipv6-to-nibbles (negate-prefix-p) + "Replace an IPv6 address around or preceeding point by its +ip6.arpa-representation for use in reverse zone files. The +replaced address is placed in the kill ring. + +The address can be a complete address (no prefix designator), can +have a normal prefix designator (e.g. /48), in which case only +the required number of nibbles are output, or can have a negative +prefix designator (e.g. /-112), in which case only the parts of +the address *not* covered by the absolute value of the prefix +length is output, as a relative address (without .ip6.arpa. at +the end). This is useful when $ORIGIN is specified in the zone +file. + +If called with a prefix argument (C-u), the value of the detected +prefix length is negated. + +Examples: + +2001:db8::12 => +2.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa. + +2001:db8::12/32 => +8.b.d.0.1.0.0.2.ip6.arpa. + +2001:db8::12/-32 => +2.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 + +::42/112 (with C-u prefix) => +2.4.0.0" + (interactive "P") + (skip-syntax-backward " ") + (skip-syntax-backward "w_.") + (re-search-forward "\\([[:xdigit:]:]+\\)\\(/-?[0-9]\\{2,3\\}\\)?") + (kill-new (match-string 0)) + (let ((address (match-string 1)) + (prefix-length (match-string 2))) + (if prefix-length + (progn + (setq prefix-length (string-to-number (substring prefix-length 1))) + (if negate-prefix-p + (setq prefix-length (* -1 prefix-length))))) + (replace-match (save-match-data + (dns-mode-reverse-and-expand-ipv6 address prefix-length))))) + +(defun dns-mode-reverse-and-expand-ipv6 (address &optional prefix-length) + "Convert an IPv6 address to (parts of) an ip6.arpa nibble format. + +ADDRESS must be an IPv6 address in the usual colon-separaated +format, without a prefix designator at the end. PREFIX-LENGTH, +if given, must be a number whose absolute value is the length in +bits of the network part of the address. + +If PREFIX-LENGTH is `nil', an absolute address is returned +representing the full IPv6 address. + +If PREFIX-LENGTH is positive, an absolute address is returned +representing the network prefix indicated. + +If PREFIX-LENGTH is negative, a relative address is returned +representing the host parts of the address with respect to the +indicated network prefix. + +See `dns-mode-ipv6-to-nibbles' for examples." + (let* ((chunks (split-string address ":")) + (prefix-length-nibbles (if prefix-length + (ceiling (abs prefix-length) 4) + 32)) + (filler-chunks (- 8 (length (remove "" chunks)))) + (expanded-address (apply #'concat + (loop with filler-done = nil + for chunk in chunks + if (and (not filler-done) + (string= "" chunk)) + append (prog1 + (loop repeat filler-chunks + collect "0000") + (setq filler-done t)) + else + if (not (string= "" chunk)) + collect (format "%04x" (string-to-number chunk 16))))) + (rev-address-nibbles (nreverse (if (and prefix-length + (minusp prefix-length)) + (substring expanded-address prefix-length-nibbles) + (substring expanded-address 0 prefix-length-nibbles))))) + (with-temp-buffer + (loop for char across rev-address-nibbles + do + (insert char) + (insert ".")) + (if (and prefix-length + (minusp prefix-length)) + (delete-char -1) + (insert "ip6.arpa.")) + (insert " ") + (buffer-string)))) + (provide 'dns-mode) ;;; dns-mode.el ends here diff --git a/test/lisp/textmodes/dns-mode-tests.el b/test/lisp/textmodes/dns-mode-tests.el new file mode 100644 index 0000000000..34e86201d8 --- /dev/null +++ b/test/lisp/textmodes/dns-mode-tests.el @@ -0,0 +1,58 @@ +;;; dns-mode-tests.el --- Test suite for dns-mode -*- lexical-binding: t; -*- + +;; Copyright (C) 2017 Free Software Foundation, Inc. + +;; Author: Peder O. Klingenberg +;; Keywords: dns zone + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see . + +;;; Code: + +(require 'ert) +(require 'dns-mode) + +;;; IPv6 reverse zones +(ert-deftest dns-mode-ipv6-conversion () + (let ((address "2001:db8::42")) + (should (equal (dns-mode-reverse-and-expand-ipv6 address) + "2.4.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa. ")) + (should (equal (dns-mode-reverse-and-expand-ipv6 address 32) + "8.b.d.0.1.0.0.2.ip6.arpa. ")) + (should (equal (dns-mode-reverse-and-expand-ipv6 address -112) + "2.4.0.0 ")))) + +(ert-deftest dns-mode-ipv6-text-replacement () + (let ((address "2001:db8::42/32")) + (with-temp-buffer + ;; Conversion with point directly after address + (insert address) + (dns-mode-ipv6-to-nibbles nil) + (should (equal (buffer-string) "8.b.d.0.1.0.0.2.ip6.arpa. ")) + ;; Kill ring contains the expected + (erase-buffer) + (yank) + (should (equal (buffer-string) address)) + ;; Point at beginning of address (and prefix arg to command) + (goto-char (point-min)) + (dns-mode-ipv6-to-nibbles t) + (should (equal (buffer-string) "2.4.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 ")) + ;; Point separated from address by whitespace + (erase-buffer) + (insert address) + (insert " ") + (dns-mode-ipv6-to-nibbles nil) + (should (equal (buffer-string) "8.b.d.0.1.0.0.2.ip6.arpa. "))))) -- 2.11.0