networking.scm 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2017 Thomas Danckaert <post@thomasdanckaert.be>
  3. ;;; Copyright © 2017, 2020 Marius Bakke <marius@gnu.org>
  4. ;;; Copyright © 2018 Chris Marusich <cmmarusich@gmail.com>
  5. ;;; Copyright © 2018 Arun Isaac <arunisaac@systemreboot.net>
  6. ;;;
  7. ;;; This file is part of GNU Guix.
  8. ;;;
  9. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  10. ;;; under the terms of the GNU General Public License as published by
  11. ;;; the Free Software Foundation; either version 3 of the License, or (at
  12. ;;; your option) any later version.
  13. ;;;
  14. ;;; GNU Guix is distributed in the hope that it will be useful, but
  15. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  16. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. ;;; GNU General Public License for more details.
  18. ;;;
  19. ;;; You should have received a copy of the GNU General Public License
  20. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  21. (define-module (gnu tests networking)
  22. #:use-module (gnu tests)
  23. #:use-module (gnu system)
  24. #:use-module (gnu system vm)
  25. #:use-module (gnu services)
  26. #:use-module (gnu services base)
  27. #:use-module (gnu services networking)
  28. #:use-module (guix gexp)
  29. #:use-module (guix store)
  30. #:use-module (guix monads)
  31. #:use-module (gnu packages bash)
  32. #:use-module (gnu packages linux)
  33. #:use-module (gnu packages networking)
  34. #:use-module (gnu services shepherd)
  35. #:use-module (ice-9 match)
  36. #:export (%test-inetd %test-openvswitch %test-dhcpd %test-tor %test-iptables))
  37. (define %inetd-os
  38. ;; Operating system with 2 inetd services.
  39. (simple-operating-system
  40. (service dhcp-client-service-type)
  41. (service inetd-service-type
  42. (inetd-configuration
  43. (entries (list
  44. (inetd-entry
  45. (name "echo")
  46. (socket-type 'stream)
  47. (protocol "tcp")
  48. (wait? #f)
  49. (user "root"))
  50. (inetd-entry
  51. (name "dict")
  52. (socket-type 'stream)
  53. (protocol "tcp")
  54. (wait? #f)
  55. (user "root")
  56. (program (file-append bash
  57. "/bin/bash"))
  58. (arguments
  59. (list "bash" (plain-file "my-dict.sh" "\
  60. while read line
  61. do
  62. if [[ $line =~ ^DEFINE\\ (.*)$ ]]
  63. then
  64. case ${BASH_REMATCH[1]} in
  65. Guix)
  66. echo GNU Guix is a package management tool for the GNU system.
  67. ;;
  68. G-expression)
  69. echo Like an S-expression but with a G.
  70. ;;
  71. *)
  72. echo NO DEFINITION FOUND
  73. ;;
  74. esac
  75. else
  76. echo ERROR
  77. fi
  78. done" ))))))))))
  79. (define* (run-inetd-test)
  80. "Run tests in %INETD-OS, where the inetd service provides an echo service on
  81. port 7, and a dict service on port 2628."
  82. (define os
  83. (marionette-operating-system %inetd-os))
  84. (define vm
  85. (virtual-machine
  86. (operating-system os)
  87. (port-forwardings `((8007 . 7)
  88. (8628 . 2628)))))
  89. (define test
  90. (with-imported-modules '((gnu build marionette))
  91. #~(begin
  92. (use-modules (ice-9 rdelim)
  93. (srfi srfi-64)
  94. (gnu build marionette))
  95. (define marionette
  96. (make-marionette (list #$vm)))
  97. (mkdir #$output)
  98. (chdir #$output)
  99. (test-begin "inetd")
  100. ;; Make sure the PID file is created.
  101. (test-assert "PID file"
  102. (marionette-eval
  103. '(file-exists? "/var/run/inetd.pid")
  104. marionette))
  105. ;; Test the echo service.
  106. (test-equal "echo response"
  107. "Hello, Guix!"
  108. (let ((echo (socket PF_INET SOCK_STREAM 0))
  109. (addr (make-socket-address AF_INET INADDR_LOOPBACK 8007)))
  110. (connect echo addr)
  111. (display "Hello, Guix!\n" echo)
  112. (let ((response (read-line echo)))
  113. (close echo)
  114. response)))
  115. ;; Test the dict service
  116. (test-equal "dict response"
  117. "GNU Guix is a package management tool for the GNU system."
  118. (let ((dict (socket PF_INET SOCK_STREAM 0))
  119. (addr (make-socket-address AF_INET INADDR_LOOPBACK 8628)))
  120. (connect dict addr)
  121. (display "DEFINE Guix\n" dict)
  122. (let ((response (read-line dict)))
  123. (close dict)
  124. response)))
  125. (test-end)
  126. (exit (= (test-runner-fail-count (test-runner-current)) 0)))))
  127. (gexp->derivation "inetd-test" test))
  128. (define %test-inetd
  129. (system-test
  130. (name "inetd")
  131. (description "Connect to a host with an INETD server.")
  132. (value (run-inetd-test))))
  133. ;;;
  134. ;;; Open vSwitch
  135. ;;;
  136. (define setup-openvswitch
  137. #~(let ((ovs-vsctl (lambda (str)
  138. (zero? (apply system*
  139. #$(file-append openvswitch "/bin/ovs-vsctl")
  140. (string-tokenize str)))))
  141. (add-native-port (lambda (if)
  142. (string-append "--may-exist add-port br0 " if
  143. " vlan_mode=native-untagged"
  144. " -- set Interface " if
  145. " type=internal"))))
  146. (and (ovs-vsctl "--may-exist add-br br0")
  147. ;; Connect eth0 as an "untagged" port (no VLANs).
  148. (ovs-vsctl "--may-exist add-port br0 eth0 vlan_mode=native-untagged")
  149. (ovs-vsctl (add-native-port "ovs0")))))
  150. (define openvswitch-configuration-service
  151. (simple-service 'openvswitch-configuration shepherd-root-service-type
  152. (list (shepherd-service
  153. (provision '(openvswitch-configuration))
  154. (requirement '(vswitchd))
  155. (start #~(lambda ()
  156. #$setup-openvswitch))
  157. (respawn? #f)))))
  158. (define %openvswitch-os
  159. (operating-system
  160. (inherit (simple-operating-system
  161. (static-networking-service "ovs0" "10.1.1.1"
  162. #:netmask "255.255.255.252"
  163. #:requirement '(openvswitch-configuration))
  164. (service openvswitch-service-type)
  165. openvswitch-configuration-service))
  166. ;; Ensure the interface name does not change depending on the driver.
  167. (kernel-arguments (cons "net.ifnames=0" %default-kernel-arguments))))
  168. (define (run-openvswitch-test)
  169. (define os
  170. (marionette-operating-system %openvswitch-os
  171. #:imported-modules '((gnu services herd))))
  172. (define test
  173. (with-imported-modules '((gnu build marionette))
  174. #~(begin
  175. (use-modules (gnu build marionette)
  176. (ice-9 popen)
  177. (ice-9 rdelim)
  178. (srfi srfi-64))
  179. (define marionette
  180. (make-marionette (list #$(virtual-machine os))))
  181. (mkdir #$output)
  182. (chdir #$output)
  183. (test-begin "openvswitch")
  184. ;; Make sure the bridge is created.
  185. (test-assert "br0 exists"
  186. (marionette-eval
  187. '(zero? (system* #$(file-append openvswitch "/bin/ovs-vsctl")
  188. "br-exists" "br0"))
  189. marionette))
  190. ;; Make sure eth0 is connected to the bridge.
  191. (test-equal "eth0 is connected to br0"
  192. "br0"
  193. (marionette-eval
  194. '(begin
  195. (use-modules (ice-9 popen) (ice-9 rdelim))
  196. (let* ((port (open-pipe*
  197. OPEN_READ
  198. (string-append #$openvswitch "/bin/ovs-vsctl")
  199. "port-to-br" "eth0"))
  200. (output (read-line port)))
  201. (close-pipe port)
  202. output))
  203. marionette))
  204. ;; Make sure the virtual interface got a static IP.
  205. (test-assert "networking has started on ovs0"
  206. (marionette-eval
  207. '(begin
  208. (use-modules (gnu services herd)
  209. (srfi srfi-1))
  210. (live-service-running
  211. (find (lambda (live)
  212. (memq 'networking-ovs0
  213. (live-service-provision live)))
  214. (current-services))))
  215. marionette))
  216. (test-end)
  217. (exit (= (test-runner-fail-count (test-runner-current)) 0)))))
  218. (gexp->derivation "openvswitch-test" test))
  219. (define %test-openvswitch
  220. (system-test
  221. (name "openvswitch")
  222. (description "Test a running OpenvSwitch configuration.")
  223. (value (run-openvswitch-test))))
  224. ;;;
  225. ;;; DHCP Daemon
  226. ;;;
  227. (define minimal-dhcpd-v4-config-file
  228. (plain-file "dhcpd.conf"
  229. "\
  230. default-lease-time 600;
  231. max-lease-time 7200;
  232. subnet 192.168.1.0 netmask 255.255.255.0 {
  233. range 192.168.1.100 192.168.1.200;
  234. option routers 192.168.1.1;
  235. option domain-name-servers 192.168.1.2, 192.168.1.3;
  236. option domain-name \"dummy.domain.name.abc123xyz\";
  237. }
  238. "))
  239. (define dhcpd-v4-configuration
  240. (dhcpd-configuration
  241. (config-file minimal-dhcpd-v4-config-file)
  242. (version "4")
  243. (interfaces '("ens3"))))
  244. (define %dhcpd-os
  245. (simple-operating-system
  246. (static-networking-service "ens3" "192.168.1.4"
  247. #:netmask "255.255.255.0"
  248. #:gateway "192.168.1.1"
  249. #:name-servers '("192.168.1.2" "192.168.1.3"))
  250. (service dhcpd-service-type dhcpd-v4-configuration)))
  251. (define (run-dhcpd-test)
  252. (define os
  253. (marionette-operating-system %dhcpd-os
  254. #:imported-modules '((gnu services herd))))
  255. (define test
  256. (with-imported-modules '((gnu build marionette))
  257. #~(begin
  258. (use-modules (gnu build marionette)
  259. (ice-9 popen)
  260. (ice-9 rdelim)
  261. (srfi srfi-64))
  262. (define marionette
  263. (make-marionette (list #$(virtual-machine os))))
  264. (mkdir #$output)
  265. (chdir #$output)
  266. (test-begin "dhcpd")
  267. (test-assert "pid file exists"
  268. (marionette-eval
  269. '(file-exists?
  270. #$(dhcpd-configuration-pid-file dhcpd-v4-configuration))
  271. marionette))
  272. (test-assert "lease file exists"
  273. (marionette-eval
  274. '(file-exists?
  275. #$(dhcpd-configuration-lease-file dhcpd-v4-configuration))
  276. marionette))
  277. (test-assert "run directory exists"
  278. (marionette-eval
  279. '(file-exists?
  280. #$(dhcpd-configuration-run-directory dhcpd-v4-configuration))
  281. marionette))
  282. (test-assert "dhcpd is alive"
  283. (marionette-eval
  284. '(begin
  285. (use-modules (gnu services herd)
  286. (srfi srfi-1))
  287. (live-service-running
  288. (find (lambda (live)
  289. (memq 'dhcpv4-daemon
  290. (live-service-provision live)))
  291. (current-services))))
  292. marionette))
  293. (test-end)
  294. (exit (= (test-runner-fail-count (test-runner-current)) 0)))))
  295. (gexp->derivation "dhcpd-test" test))
  296. (define %test-dhcpd
  297. (system-test
  298. (name "dhcpd")
  299. (description "Test a running DHCP daemon configuration.")
  300. (value (run-dhcpd-test))))
  301. ;;;
  302. ;;; Services related to Tor
  303. ;;;
  304. (define %tor-os
  305. (simple-operating-system
  306. (service tor-service-type)))
  307. (define %tor-os/unix-socks-socket
  308. (simple-operating-system
  309. (service tor-service-type
  310. (tor-configuration
  311. (socks-socket-type 'unix)))))
  312. (define (run-tor-test)
  313. (define os
  314. (marionette-operating-system %tor-os
  315. #:imported-modules '((gnu services herd))
  316. #:requirements '(tor)))
  317. (define os/unix-socks-socket
  318. (marionette-operating-system %tor-os/unix-socks-socket
  319. #:imported-modules '((gnu services herd))
  320. #:requirements '(tor)))
  321. (define test
  322. (with-imported-modules '((gnu build marionette))
  323. #~(begin
  324. (use-modules (gnu build marionette)
  325. (ice-9 popen)
  326. (ice-9 rdelim)
  327. (srfi srfi-64))
  328. (define marionette
  329. (make-marionette (list #$(virtual-machine os))))
  330. (define (tor-is-alive? marionette)
  331. (marionette-eval
  332. '(begin
  333. (use-modules (gnu services herd)
  334. (srfi srfi-1))
  335. (live-service-running
  336. (find (lambda (live)
  337. (memq 'tor
  338. (live-service-provision live)))
  339. (current-services))))
  340. marionette))
  341. (mkdir #$output)
  342. (chdir #$output)
  343. (test-begin "tor")
  344. ;; Test the usual Tor service.
  345. (test-assert "tor is alive"
  346. (tor-is-alive? marionette))
  347. (test-assert "tor is listening"
  348. (let ((default-port 9050))
  349. (wait-for-tcp-port default-port marionette)))
  350. ;; Don't run two VMs at once.
  351. (marionette-control "quit" marionette)
  352. ;; Test the Tor service using a SOCKS socket.
  353. (let* ((socket-directory "/tmp/more-sockets")
  354. (_ (mkdir socket-directory))
  355. (marionette/unix-socks-socket
  356. (make-marionette
  357. (list #$(virtual-machine os/unix-socks-socket))
  358. ;; We can't use the same socket directory as the first
  359. ;; marionette.
  360. #:socket-directory socket-directory)))
  361. (test-assert "tor is alive, even when using a SOCKS socket"
  362. (tor-is-alive? marionette/unix-socks-socket))
  363. (test-assert "tor is listening, even when using a SOCKS socket"
  364. (wait-for-unix-socket "/var/run/tor/socks-sock"
  365. marionette/unix-socks-socket)))
  366. (test-end)
  367. (exit (= (test-runner-fail-count (test-runner-current)) 0)))))
  368. (gexp->derivation "tor-test" test))
  369. (define %test-tor
  370. (system-test
  371. (name "tor")
  372. (description "Test a running Tor daemon configuration.")
  373. (value (run-tor-test))))
  374. (define* (run-iptables-test)
  375. "Run tests of 'iptables-service-type'."
  376. (define iptables-rules
  377. "*filter
  378. :INPUT ACCEPT
  379. :FORWARD ACCEPT
  380. :OUTPUT ACCEPT
  381. -A INPUT -p tcp -m tcp --dport 7 -j REJECT --reject-with icmp-port-unreachable
  382. COMMIT
  383. ")
  384. (define ip6tables-rules
  385. "*filter
  386. :INPUT ACCEPT
  387. :FORWARD ACCEPT
  388. :OUTPUT ACCEPT
  389. -A INPUT -p tcp -m tcp --dport 7 -j REJECT --reject-with icmp6-port-unreachable
  390. COMMIT
  391. ")
  392. (define inetd-echo-port 7)
  393. (define os
  394. (marionette-operating-system
  395. (simple-operating-system
  396. (service dhcp-client-service-type)
  397. (service inetd-service-type
  398. (inetd-configuration
  399. (entries (list
  400. (inetd-entry
  401. (name "echo")
  402. (socket-type 'stream)
  403. (protocol "tcp")
  404. (wait? #f)
  405. (user "root"))))))
  406. (service iptables-service-type
  407. (iptables-configuration
  408. (ipv4-rules (plain-file "iptables.rules" iptables-rules))
  409. (ipv6-rules (plain-file "ip6tables.rules" ip6tables-rules)))))
  410. #:imported-modules '((gnu services herd))
  411. #:requirements '(inetd iptables)))
  412. (define test
  413. (with-imported-modules '((gnu build marionette))
  414. #~(begin
  415. (use-modules (srfi srfi-64)
  416. (gnu build marionette))
  417. (define marionette
  418. (make-marionette (list #$(virtual-machine os))))
  419. (define (dump-iptables iptables-save marionette)
  420. (marionette-eval
  421. `(begin
  422. (use-modules (ice-9 popen)
  423. (ice-9 rdelim)
  424. (ice-9 regex))
  425. (call-with-output-string
  426. (lambda (out)
  427. (call-with-port
  428. (open-pipe* OPEN_READ ,iptables-save)
  429. (lambda (in)
  430. (let loop ((line (read-line in)))
  431. ;; iptables-save does not output rules in the exact
  432. ;; same format we loaded using iptables-restore. It
  433. ;; adds comments, packet counters, etc. We remove
  434. ;; these additions.
  435. (unless (eof-object? line)
  436. (cond
  437. ;; Remove comments
  438. ((string-match "^#" line) #t)
  439. ;; Remove packet counters
  440. ((string-match "^:([A-Z]*) ([A-Z]*) .*" line)
  441. => (lambda (match-record)
  442. (format out ":~a ~a~%"
  443. (match:substring match-record 1)
  444. (match:substring match-record 2))))
  445. ;; Pass other lines without modification
  446. (else (display line out)
  447. (newline out)))
  448. (loop (read-line in)))))))))
  449. marionette))
  450. (mkdir #$output)
  451. (chdir #$output)
  452. (test-begin "iptables")
  453. (test-equal "iptables-save dumps the same rules that were loaded"
  454. (dump-iptables #$(file-append iptables "/sbin/iptables-save")
  455. marionette)
  456. #$iptables-rules)
  457. (test-equal "ip6tables-save dumps the same rules that were loaded"
  458. (dump-iptables #$(file-append iptables "/sbin/ip6tables-save")
  459. marionette)
  460. #$ip6tables-rules)
  461. (test-error "iptables firewall blocks access to inetd echo service"
  462. 'misc-error
  463. (wait-for-tcp-port inetd-echo-port marionette #:timeout 5))
  464. ;; TODO: This test freezes up at the login prompt without any
  465. ;; relevant messages on the console. Perhaps it is waiting for some
  466. ;; timeout. Find and fix this issue.
  467. ;; (test-assert "inetd echo service is accessible after iptables firewall is stopped"
  468. ;; (begin
  469. ;; (marionette-eval
  470. ;; '(begin
  471. ;; (use-modules (gnu services herd))
  472. ;; (stop-service 'iptables))
  473. ;; marionette)
  474. ;; (wait-for-tcp-port inetd-echo-port marionette #:timeout 5)))
  475. (test-end)
  476. (exit (= (test-runner-fail-count (test-runner-current)) 0)))))
  477. (gexp->derivation "iptables" test))
  478. (define %test-iptables
  479. (system-test
  480. (name "iptables")
  481. (description "Test a running iptables daemon.")
  482. (value (run-iptables-test))))