avahi.scm 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2020 Mathieu Othacehe <othacehe@gnu.org>
  3. ;;;
  4. ;;; This file is part of GNU Guix.
  5. ;;;
  6. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  7. ;;; under the terms of the GNU General Public License as published by
  8. ;;; the Free Software Foundation; either version 3 of the License, or (at
  9. ;;; your option) any later version.
  10. ;;;
  11. ;;; GNU Guix is distributed in the hope that it will be useful, but
  12. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. ;;; GNU General Public License for more details.
  15. ;;;
  16. ;;; You should have received a copy of the GNU General Public License
  17. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  18. (define-module (guix avahi)
  19. #:use-module (guix records)
  20. #:use-module (guix build syscalls)
  21. #:use-module (avahi)
  22. #:use-module (avahi client)
  23. #:use-module (avahi client lookup)
  24. #:use-module (avahi client publish)
  25. #:use-module (srfi srfi-9)
  26. #:use-module (ice-9 threads)
  27. #:export (avahi-service
  28. avahi-service?
  29. avahi-service-name
  30. avahi-service-type
  31. avahi-service-interface
  32. avahi-service-local-address
  33. avahi-service-address
  34. avahi-service-port
  35. avahi-service-txt
  36. avahi-publish-service-thread
  37. avahi-browse-service-thread))
  38. (define-record-type* <avahi-service>
  39. avahi-service make-avahi-service
  40. avahi-service?
  41. (name avahi-service-name)
  42. (type avahi-service-type)
  43. (interface avahi-service-interface)
  44. (local-address avahi-service-local-address)
  45. (address avahi-service-address)
  46. (port avahi-service-port)
  47. (txt avahi-service-txt))
  48. (define never
  49. ;; Never true.
  50. (const #f))
  51. (define* (avahi-publish-service-thread name
  52. #:key
  53. type port
  54. (stop-loop? never)
  55. (timeout (if (eq? stop-loop? never)
  56. #f
  57. 500))
  58. (txt '()))
  59. "Publish the service TYPE using Avahi, for the given PORT, on all interfaces
  60. and for all protocols. Also, advertise the given TXT record list.
  61. This procedure starts a new thread running the Avahi event loop. It exits
  62. when STOP-LOOP? procedure returns true."
  63. (define client-callback
  64. (lambda (client state)
  65. (when (eq? state client-state/s-running)
  66. (let ((group (make-entry-group client (const #t))))
  67. (apply
  68. add-entry-group-service! group interface/unspecified
  69. protocol/unspecified '()
  70. name type #f #f port txt)
  71. (commit-entry-group group)))))
  72. (call-with-new-thread
  73. (lambda ()
  74. (let* ((poll (make-simple-poll))
  75. (client (make-client (simple-poll poll)
  76. (list
  77. client-flag/ignore-user-config)
  78. client-callback)))
  79. (while (not (stop-loop?))
  80. (if timeout
  81. (iterate-simple-poll poll timeout)
  82. (iterate-simple-poll poll)))))))
  83. (define (interface->ip-address interface)
  84. "Return the local IP address of the given INTERFACE."
  85. (let* ((socket (socket AF_INET SOCK_STREAM 0))
  86. (address (network-interface-address socket interface))
  87. (ip (inet-ntop (sockaddr:fam address)
  88. (sockaddr:addr address))))
  89. (close-port socket)
  90. ip))
  91. (define* (avahi-browse-service-thread proc
  92. #:key
  93. types
  94. (ignore-local? #t)
  95. (family AF_INET)
  96. (stop-loop? never)
  97. (timeout (if (eq? stop-loop? never)
  98. #f
  99. 500)))
  100. "Browse services which type is part of the TYPES list, using Avahi. The
  101. search is restricted to services with the given FAMILY. Each time a service
  102. is found or removed, PROC is called and passed as argument the corresponding
  103. AVAHI-SERVICE record. If a service is available on multiple network
  104. interfaces, it will only be reported on the first interface found.
  105. This procedure starts a new thread running the Avahi event loop. It exits
  106. when STOP-LOOP? procedure returns true."
  107. (define %known-hosts
  108. ;; Set of Avahi discovered hosts.
  109. (make-hash-table))
  110. (define (service-resolver-callback resolver interface protocol event
  111. service-name service-type domain
  112. host-name address-type address port
  113. txt flags)
  114. ;; Handle service resolution events.
  115. (cond ((eq? event resolver-event/found)
  116. ;; Add the service if the host is unknown. This means that if a
  117. ;; service is available on multiple network interfaces for a single
  118. ;; host, only the first interface found will be considered.
  119. (unless (or (hash-ref %known-hosts service-name)
  120. (and ignore-local?
  121. (member lookup-result-flag/local flags)))
  122. (let* ((address (inet-ntop family address))
  123. (local-address (interface->ip-address interface))
  124. (service* (avahi-service
  125. (name service-name)
  126. (type service-type)
  127. (interface interface)
  128. (local-address local-address)
  129. (address address)
  130. (port port)
  131. (txt txt))))
  132. (hash-set! %known-hosts service-name service*)
  133. (proc 'new-service service*))))
  134. ((eq? event resolver-event/failure)
  135. ;; Failure to resolve the host associated with a service. This
  136. ;; usually means that the mDNS record hasn't expired yet but that
  137. ;; the host went off-line.
  138. (let ((service (hash-ref %known-hosts service-name)))
  139. (when service
  140. (proc 'remove-service service)
  141. (hash-remove! %known-hosts service-name)))))
  142. (free-service-resolver! resolver))
  143. (define (service-browser-callback browser interface protocol event
  144. service-name service-type
  145. domain flags)
  146. (cond
  147. ((eq? event browser-event/new)
  148. (make-service-resolver (service-browser-client browser)
  149. interface protocol
  150. service-name service-type domain
  151. protocol/unspecified '()
  152. service-resolver-callback))
  153. ((eq? event browser-event/remove)
  154. (let ((service (hash-ref %known-hosts service-name)))
  155. (when service
  156. (proc 'remove-service service)
  157. (hash-remove! %known-hosts service-name))))))
  158. (define client-callback
  159. (lambda (client state)
  160. (if (eq? state client-state/s-running)
  161. (for-each (lambda (type)
  162. (make-service-browser client
  163. interface/unspecified
  164. protocol/inet
  165. type #f '()
  166. service-browser-callback))
  167. types))))
  168. (let* ((poll (make-simple-poll))
  169. (client (make-client (simple-poll poll)
  170. '() ;; no flags
  171. client-callback)))
  172. (and (client? client)
  173. (while (not (stop-loop?))
  174. (if timeout
  175. (iterate-simple-poll poll timeout)
  176. (iterate-simple-poll poll))))))