~ruther/guix-local

9260b9d1005559f526569bcf694e9c9b40d85800 — Thomas Danckaert 9 years ago 1c17a86
services: Add inetd-service-type.

* gnu/services/networking.scm (<inetd-configuration>, <inetd-entry>): New
record types.
(inetd-config-file, inetd-shepherd-service): New procedures.
(inetd-service-type): New variable.
* doc/guix.texi (Networking Services): Document it.
* gnu/tests/networking.scm: New file.
* gnu/local.mk: Add it.
4 files changed, 334 insertions(+), 1 deletions(-)

M doc/guix.texi
M gnu/local.mk
M gnu/services/networking.scm
A gnu/tests/networking.scm
M doc/guix.texi => doc/guix.texi +95 -1
@@ 33,7 33,8 @@ Copyright @copyright{} 2016 Alex ter Weele@*
Copyright @copyright{} 2017 Clément Lassieur@*
Copyright @copyright{} 2017 Mathieu Othacehe@*
Copyright @copyright{} 2017 Federico Beffa@*
Copyright @copyright{} 2017 Carlo Zancanaro
Copyright @copyright{} 2017 Carlo Zancanaro@*
Copyright @copyright{} 2017 Thomas Danckaert

Permission is granted to copy, distribute and/or modify this document
under the terms of the GNU Free Documentation License, Version 1.3 or


@@ 9400,6 9401,99 @@ make an initial adjustment of more than 1,000 seconds.
List of host names used as the default NTP servers.
@end defvr

@cindex inetd
@deffn {Scheme variable} inetd-service-type
This service runs the @command{inetd} (@pxref{inetd invocation,,,
inetutils, GNU Inetutils}) daemon.  @command{inetd} listens for
connections on internet sockets, and lazily starts the specified server
program when a connection is made on one of these sockets.

The value of this service is an @code{inetd-configuration} object.  The
following example configures the @command{inetd} daemon to provide the
built-in @command{echo} service, as well as an smtp service which
forwards smtp traffic over ssh to a server @code{smtp-server} behind a
gateway @code{hostname}:

@example
(service
 inetd-service-type
 (inetd-configuration
  (entries (list
            (inetd-entry
             (name "echo")
             (socket-type 'stream)
             (protocol "tcp")
             (wait? #f)
             (user "root"))
            (inetd-entry
             (node "127.0.0.1")
             (name "smtp")
             (socket-type 'stream)
             (protocol "tcp")
             (wait? #f)
             (user "root")
             (program (file-append openssh "/bin/ssh"))
             (arguments
              '("ssh" "-qT" "-i" "/path/to/ssh_key"
                "-W" "smtp-server:25" "user@@hostname")))))
@end example

See below for more details about @code{inetd-configuration}.
@end deffn

@deftp {Data Type} inetd-configuration
Data type representing the configuration of @command{inetd}.

@table @asis
@item @code{program} (default: @code{(file-append inetutils "/libexec/inetd")})
The @command{inetd} executable to use.

@item @code{entries} (default: @code{'()})
A list of @command{inetd} service entries.  Each entry should be created
by the @code{inetd-entry} constructor.
@end table
@end deftp

@deftp {Data Type} inetd-entry
Data type representing an entry in the @command{inetd} configuration.
Each entry corresponds to a socket where @command{inetd} will listen for
requests.

@table @asis
@item @code{node} (default: @code{#f})
Optional string, a comma-separated list of local addresses
@command{inetd} should use when listening for this service.
@xref{Configuration file,,, inetutils, GNU Inetutils} for a complete
description of all options.
@item @code{name}
A string, the name must correspond to an entry in @code{/etc/services}.
@item @code{socket-type}
One of @code{'stream}, @code{'dgram}, @code{'raw}, @code{'rdm} or
@code{'seqpacket}.
@item @code{protocol}
A string, must correspond to an entry in @code{/etc/protocols}.
@item @code{wait?} (default: @code{#t})
Whether @command{inetd} should wait for the server to exit before
listening to new service requests.
@item @code{user}
A string containing the user (and, optionally, group) name of the user
as whom the server should run.  The group name can be specified in a
suffix, separated by a colon or period, i.e. @code{"user"},
@code{"user:group"} or @code{"user.group"}.
@item @code{program} (default: @code{"internal"})
The server program which will serve the requests, or @code{"internal"}
if @command{inetd} should use a built-in service.
@item @code{arguments} (default: @code{'()})
A list strings or file-like objects, which are the server program's
arguments, starting with the zeroth argument, i.e. the name of the
program itself.  For @command{inetd}'s internal services, this entry
must be @code{'()} or @code{'("internal")}.
@end table

@xref{Configuration file,,, inetutils, GNU Inetutils} for a more
detailed discussion of each configuration field.
@end deftp

@cindex Tor
@deffn {Scheme Procedure} tor-service [@var{config-file}] [#:tor @var{tor}]
Return a service to run the @uref{https://torproject.org, Tor} anonymous

M gnu/local.mk => gnu/local.mk +1 -0
@@ 464,6 464,7 @@ GNU_SYSTEM_MODULES =				\
  %D%/tests/install.scm				\
  %D%/tests/mail.scm				\
  %D%/tests/messaging.scm			\
  %D%/tests/networking.scm			\
  %D%/tests/ssh.scm				\
  %D%/tests/web.scm


M gnu/services/networking.scm => gnu/services/networking.scm +89 -0
@@ 4,6 4,7 @@
;;; Copyright © 2016 Efraim Flashner <efraim@flashner.co.il>
;;; Copyright © 2016 John Darrington <jmd@gnu.org>
;;; Copyright © 2017 Clément Lassieur <clement@lassieur.org>
;;; Copyright © 2017 Thomas Danckaert <post@thomasdanckaert.be>
;;;
;;; This file is part of GNU Guix.
;;;


@@ 61,6 62,10 @@
            ntp-service
            ntp-service-type

            inetd-configuration
            inetd-entry
            inetd-service-type

            tor-configuration
            tor-configuration?
            tor-hidden-service


@@ 432,6 437,90 @@ make an initial adjustment of more than 1,000 seconds."


;;;
;;; Inetd.
;;;

(define-record-type* <inetd-configuration> inetd-configuration
  make-inetd-configuration
  inetd-configuration?
  (program           inetd-configuration-program   ;file-like
                     (default (file-append inetutils "/libexec/inetd")))
  (entries           inetd-configuration-entries   ;list of <inetd-entry>
                     (default '())))

(define-record-type* <inetd-entry> inetd-entry make-inetd-entry
  inetd-entry?
  (node              inetd-entry-node         ;string or #f
                     (default #f))
  (name              inetd-entry-name)        ;string, from /etc/services

  (socket-type       inetd-entry-socket-type) ;stream | dgram | raw |
                                              ;rdm | seqpacket
  (protocol          inetd-entry-protocol)    ;string, from /etc/protocols

  (wait?             inetd-entry-wait?        ;Boolean
                     (default #t))
  (user              inetd-entry-user)        ;string

  (program           inetd-entry-program      ;string or file-like object
                     (default "internal"))
  (arguments         inetd-entry-arguments    ;list of strings or file-like objects
                     (default '())))

(define (inetd-config-file entries)
  (apply mixed-text-file "inetd.conf"
         (map
          (lambda (entry)
            (let* ((node (inetd-entry-node entry))
                   (name (inetd-entry-name entry))
                   (socket
                    (if node (string-append node ":" name) name))
                   (type
                    (match (inetd-entry-socket-type entry)
                      ((or 'stream 'dgram 'raw 'rdm 'seqpacket)
                       (symbol->string (inetd-entry-socket-type entry)))))
                   (protocol (inetd-entry-protocol entry))
                   (wait (if (inetd-entry-wait? entry) "wait" "nowait"))
                   (user (inetd-entry-user entry))
                   (program (inetd-entry-program entry))
                   (args (inetd-entry-arguments entry)))
              #~(string-append
                 (string-join
                  (list #$@(list socket type protocol wait user program) #$@args)
                  " ") "\n")))
          entries)))

(define inetd-shepherd-service
  (match-lambda
    (($ <inetd-configuration> program ()) '()) ; empty list of entries -> do nothing
    (($ <inetd-configuration> program entries)
     (list
      (shepherd-service
       (documentation "Run inetd.")
       (provision '(inetd))
       (requirement '(user-processes networking syslogd))
       (start #~(make-forkexec-constructor
                 (list #$program #$(inetd-config-file entries))
                 #:pid-file "/var/run/inetd.pid"))
       (stop #~(make-kill-destructor)))))))

(define-public inetd-service-type
  (service-type
   (name 'inetd)
   (extensions
    (list (service-extension shepherd-root-service-type
                             inetd-shepherd-service)))

   ;; The service can be extended with additional lists of entries.
   (compose concatenate)
   (extend (lambda (config entries)
             (inetd-configuration
              (inherit config)
              (entries (append (inetd-configuration-entries config)
                               entries)))))))


;;;
;;; Tor.
;;;


A gnu/tests/networking.scm => gnu/tests/networking.scm +149 -0
@@ 0,0 1,149 @@
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2017 Thomas Danckaert <post@thomasdanckaert.be>
;;;
;;; This file is part of GNU Guix.
;;;
;;; GNU Guix 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 Guix 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 Guix.  If not, see <http://www.gnu.org/licenses/>.

(define-module (gnu tests networking)
  #:use-module (gnu tests)
  #:use-module (gnu system)
  #:use-module (gnu system grub)
  #:use-module (gnu system file-systems)
  #:use-module (gnu system shadow)
  #:use-module (gnu system vm)
  #:use-module (gnu services)
  #:use-module (gnu services base)
  #:use-module (gnu services networking)
  #:use-module (guix gexp)
  #:use-module (guix store)
  #:use-module (guix monads)
  #:use-module (gnu packages bash)
  #:export (%test-inetd))

(define %inetd-os
  ;; Operating system with 2 inetd services.
  (operating-system
    (host-name "komputilo")
    (timezone "Europe/Brussels")
    (locale "en_US.utf8")

    (bootloader (grub-configuration (device "/dev/sdX")))
    (file-systems %base-file-systems)
    (firmware '())
    (users %base-user-accounts)
    (services (cons* (dhcp-client-service)
                     (service inetd-service-type
                              (inetd-configuration
                               (entries (list
                                         (inetd-entry
                                          (name "echo")
                                          (socket-type 'stream)
                                          (protocol "tcp")
                                          (wait? #f)
                                          (user "root"))
                                         (inetd-entry
                                          (name "dict")
                                          (socket-type 'stream)
                                          (protocol "tcp")
                                          (wait? #f)
                                          (user "root")
                                          (program (file-append bash
                                                                "/bin/bash"))
                                          (arguments
                                           (list "bash" (plain-file "my-dict.sh" "\
while read line
do
    if [[ $line =~ ^DEFINE\\ (.*)$ ]]
    then
        case ${BASH_REMATCH[1]} in
            Guix)
                echo GNU Guix is a package management tool for the GNU system.
                ;;
            G-expression)
                echo Like an S-expression but with a G.
                ;;
            *)
                echo NO DEFINITION FOUND
                ;;
        esac
    else
        echo ERROR
    fi
done" ))))))))
                     %base-services))))

(define* (run-inetd-test)
  "Run tests in %INETD-OS, where the inetd service provides an echo service on
port 7, and a dict service on port 2628."
  (mlet* %store-monad ((os -> (marionette-operating-system %inetd-os))
                       (command (system-qemu-image/shared-store-script
                                 os #:graphic? #f)))
    (define test
      (with-imported-modules '((gnu build marionette))
        #~(begin
            (use-modules (ice-9 rdelim)
                         (srfi srfi-64)
                         (gnu build marionette))
            (define marionette
              ;; Forward guest ports 7 and 2628 to host ports 8007 and 8628.
              (make-marionette (list #$command "-net"
                                     (string-append
                                      "user"
                                      ",hostfwd=tcp::8007-:7"
                                      ",hostfwd=tcp::8628-:2628"))))

            (mkdir #$output)
            (chdir #$output)

            (test-begin "inetd")

            ;; Make sure the PID file is created.
            (test-assert "PID file"
              (marionette-eval
               '(file-exists? "/var/run/inetd.pid")
              marionette))

            ;; Test the echo service.
            (test-equal "echo response"
              "Hello, Guix!"
              (let ((echo (socket PF_INET SOCK_STREAM 0))
                    (addr (make-socket-address AF_INET INADDR_LOOPBACK 8007)))
                (connect echo addr)
                (display "Hello, Guix!\n" echo)
                (let ((response (read-line echo)))
                  (close echo)
                  response)))

            ;; Test the dict service
            (test-equal "dict response"
              "GNU Guix is a package management tool for the GNU system."
              (let ((dict (socket PF_INET SOCK_STREAM 0))
                    (addr (make-socket-address AF_INET INADDR_LOOPBACK 8628)))
                (connect dict addr)
                (display "DEFINE Guix\n" dict)
                (let ((response (read-line dict)))
                  (close dict)
                  response)))

            (test-end)
            (exit (= (test-runner-fail-count (test-runner-current)) 0)))))

    (gexp->derivation "inetd-test" test)))

(define %test-inetd
  (system-test
   (name "inetd")
   (description "Connect to a host with an INETD server.")
   (value (run-inetd-test))))