diff options
33 files changed, 1159 insertions, 522 deletions
@@ -1,10 +1,12 @@ *~ *.html *.o -arch -pacredir +compat/pacserve-announce.service config.h -avahi/pacserve.service -version.h +favicon.h +favicon.png +pacredir pacredir-*.tar.xz pacredir-*.tar.xz.asc +systemd/pacserve.service +version.h diff --git a/FLOW/fail.svg b/FLOW.d/fail.svg index e81a109..b8fa976 100644 --- a/FLOW/fail.svg +++ b/FLOW.d/fail.svg @@ -97,7 +97,7 @@ <g id="g465"> <rect - style="fill:#e6e6e6;stroke:#000000;stroke-width:0.999992;stroke-linecap:round;stroke-linejoin:round" + style="fill:#e6e6e6;stroke:#5f5f5f;stroke-width:0.999992;stroke-linecap:round;stroke-linejoin:round" id="rect849" width="55.4375" height="147.86667" @@ -174,7 +174,7 @@ <g id="g460"> <rect - style="fill:#e6e6e6;stroke:#000000;stroke-width:0.999999;stroke-linecap:round;stroke-linejoin:round" + style="fill:#e6e6e6;stroke:#5f5f5f;stroke-width:0.999999;stroke-linecap:round;stroke-linejoin:round" id="rect851" width="22.174999" height="147.86667" @@ -202,7 +202,7 @@ <g id="g454"> <rect - style="fill:#e6e6e6;stroke:#000000;stroke-width:0.999999;stroke-linecap:round;stroke-linejoin:round" + style="fill:#e6e6e6;stroke:#5f5f5f;stroke-width:0.999999;stroke-linecap:round;stroke-linejoin:round" id="rect853" width="22.174999" height="147.86667" @@ -230,7 +230,7 @@ <g id="g448"> <rect - style="fill:#e6e6e6;stroke:#000000;stroke-width:0.999999;stroke-linecap:round;stroke-linejoin:round" + style="fill:#e6e6e6;stroke:#5f5f5f;stroke-width:0.999999;stroke-linecap:round;stroke-linejoin:round" id="rect855" width="22.174999" height="147.86667" diff --git a/FLOW/ok.svg b/FLOW.d/ok.svg index 9335989..64ec56e 100644 --- a/FLOW/ok.svg +++ b/FLOW.d/ok.svg @@ -97,7 +97,7 @@ <g id="g387"> <rect - style="fill:#e6e6e6;stroke:#000000;stroke-width:0.999992;stroke-linecap:round;stroke-linejoin:round" + style="fill:#e6e6e6;stroke:#5f5f5f;stroke-width:0.999992;stroke-linecap:round;stroke-linejoin:round" id="rect849" width="55.4375" height="147.86667" @@ -174,7 +174,7 @@ <g id="g382"> <rect - style="fill:#e6e6e6;stroke:#000000;stroke-width:0.999999;stroke-linecap:round;stroke-linejoin:round" + style="fill:#e6e6e6;stroke:#5f5f5f;stroke-width:0.999999;stroke-linecap:round;stroke-linejoin:round" id="rect851" width="22.174999" height="147.86667" @@ -202,7 +202,7 @@ <g id="g376"> <rect - style="fill:#e6e6e6;stroke:#000000;stroke-width:0.999999;stroke-linecap:round;stroke-linejoin:round" + style="fill:#e6e6e6;stroke:#5f5f5f;stroke-width:0.999999;stroke-linecap:round;stroke-linejoin:round" id="rect853" width="22.174999" height="147.86667" @@ -230,7 +230,7 @@ <g id="g370"> <rect - style="fill:#e6e6e6;stroke:#000000;stroke-width:0.999999;stroke-linecap:round;stroke-linejoin:round" + style="fill:#e6e6e6;stroke:#5f5f5f;stroke-width:0.999999;stroke-linecap:round;stroke-linejoin:round" id="rect855" width="22.174999" height="147.86667" @@ -1,7 +1,7 @@ pacredir - request flow ======================= -[◀ Go back to main README](README.md) +[⬅️ Go back to main README](README.md) Whenever `pacman` sends a request to `pacredir` a number of requests (increasing with the number of hosts found) is sent through the network. @@ -17,7 +17,7 @@ For database files the decision on what return code (`307` vs. `404`) to send is made on the file age. The local file's timestamp is compared to what timestamp `pacman` sends in its request. - + After the initial request `pacman` is redirected to `host B` and gets the file there. No request is sent to the mirror. @@ -28,11 +28,11 @@ FAIL - fallback to mirror In this example no host has the file or the local database files are older than what `pacman` requested. - + All requests made by `pacredir` are answered with http code `404`, thus `pacman` receives the same. Finally `pacman` falls back to the next mirror. --- -[◀ Go back to main README](README.md) -[▲ Go back to top](#top) +[⬅️ Go back to main README](README.md) +[⬆️ Go back to top](#top) @@ -1,7 +1,7 @@ -# pacredir - redirect pacman requests, assisted by avahi service discovery +# pacredir - redirect pacman requests, assisted by mDNS Service Discovery -PREFIX := /usr -REPRODUCIBLE := 0 +PREFIX := /usr +SOURCE_DATE_EPOCH ?= $(shell date +%s) # commands CC := gcc @@ -14,10 +14,8 @@ SED := sed # flags CFLAGS += -std=c11 -O2 -fPIC -Wall -Werror -CFLAGS_EXTRA += -lpthread CFLAGS_EXTRA += $(shell pkg-config --libs --cflags libsystemd) CFLAGS_EXTRA += $(shell pkg-config --libs --cflags libcurl) -CFLAGS_EXTRA += $(shell pkg-config --libs --cflags avahi-client) CFLAGS_EXTRA += $(shell pkg-config --libs --cflags libmicrohttpd) CFLAGS_EXTRA += $(shell pkg-config --libs --cflags iniparser) LDFLAGS += -Wl,-z,now -Wl,-z,relro -pie @@ -28,58 +26,78 @@ ID := $(shell shopt -u extglob && source /etc/os-release && echo $$ID) # this is just a fallback in case you do not use git but downloaded # a release tarball... -VERSION := 0.4.7 +DISTVER := 0.7.6 +VERSION ?= $(shell git describe --long 2>/dev/null || echo $(DISTVER)) -MARKDOWN = $(wildcard *.md) -HTML = $(MARKDOWN:.md=.html) +SERVICESIN = $(wildcard */*.service.in) +SERVICES = $(SERVICESIN:.in=) +MARKDOWN = $(wildcard *.md) +HTML = $(MARKDOWN:.md=.html) -all: pacredir avahi/pacserve.service $(HTML) +all: pacredir $(SERVICES) $(HTML) -pacredir: pacredir.c pacredir.h config.h version.h - $(CC) pacredir.c $(CFLAGS) $(CFLAGS_EXTRA) $(LDFLAGS) -DREPRODUCIBLE=$(REPRODUCIBLE) -DARCH=\"$(ARCH)\" -DID=\"$(ID)\" -o pacredir +pacredir: pacredir.c pacredir.h config.h favicon.h html.h version.h + $(CC) $< $(CFLAGS) $(CFLAGS_EXTRA) $(LDFLAGS) -o $@ -config.h: - $(CP) config.def.h config.h +config.h: config.def.h + $(CP) $< $@ version.h: $(wildcard .git/HEAD .git/index .git/refs/tags/*) Makefile - printf "#ifndef VERSION\n#define VERSION \"%s\"\n#endif\n" $(shell git describe --long 2>/dev/null || echo ${VERSION}) > $@ + printf '#ifndef VERSION_H\n#define VERSION_H\n#define VERSION\t"%s"\n#define ARCH\t"%s"\n#define ID\t"%s"\n#endif\n' "${VERSION}" "$(ARCH)" "$(ID)" > $@ -avahi/pacserve.service: avahi/pacserve.service.in - $(SED) 's/%ARCH%/$(ARCH)/;s/%ID%/$(ID)/' avahi/pacserve.service.in > avahi/pacserve.service +favicon.png: logo.svg Makefile + resvg --width=32 --height=32 $< -c | oxipng - > $@ + +favicon.h: favicon.png Makefile + printf '#ifndef FAVICON_H\n#define FAVICON_H\nstatic unsigned char favicon[] = {\n' > $@ + od -t x1 -A n -v < $< | sed 's/\([0-9a-f]\{2\}\)/0x\1,/g' >> $@ + printf '};\n#define FAVICON_SHA1 "%s"\n#define FAVICON_DATE "%s"\n#endif\n' "$(shell sha1sum $< | cut -d' ' -f1)" "$(shell date --utc --date=@$(SOURCE_DATE_EPOCH) '+%a, %d %b %Y %H:%M:%S GMT')" >> $@ + +%.service: %.service.in + $(SED) 's/%ARCH%/$(ARCH)/; s/%ARCH_BYTES%/$(shell (printf $(ARCH) | wc -c; printf $(ARCH) | od -t d1 -A n) | tr -s " ")/; s/%ID%/$(ID)/; s/%ID_BYTES%/$(shell (printf $(ID) | wc -c; printf $(ID) | od -t d1 -A n) | tr -s " ")/' $< > $@ %.html: %.md Makefile markdown $< | sed 's/href="\([-[:alnum:]]*\)\.md"/href="\1.html"/g' > $@ install: install-bin install-doc -install-bin: pacredir avahi/pacserve.service +install-bin: pacredir systemd/pacserve.service $(INSTALL) -D -m0755 pacredir $(DESTDIR)$(PREFIX)/bin/pacredir $(LN) -s darkhttpd $(DESTDIR)$(PREFIX)/bin/pacserve - $(INSTALL) -D -m0644 pacredir.conf $(DESTDIR)/etc/pacredir.conf + $(INSTALL) -D -m0644 etc/pacredir.conf $(DESTDIR)/etc/pacredir.conf + $(INSTALL) -D -m0644 etc/pacserve.conf $(DESTDIR)/etc/pacserve.conf + $(INSTALL) -D -m0644 etc/01-pacredir-MulticastDNS-yes.conf $(DESTDIR)/etc/systemd/resolved.conf.d/01-pacredir-MulticastDNS-yes.conf $(INSTALL) -D -m0644 pacman/pacredir $(DESTDIR)/etc/pacman.d/pacredir - $(INSTALL) -D -m0644 avahi/pacserve.service $(DESTDIR)/etc/avahi/services/pacserve.service $(INSTALL) -D -m0644 systemd/pacredir.service $(DESTDIR)$(PREFIX)/lib/systemd/system/pacredir.service $(INSTALL) -D -m0644 systemd/pacserve.service $(DESTDIR)$(PREFIX)/lib/systemd/system/pacserve.service $(INSTALL) -D -m0644 systemd/sysusers.conf $(DESTDIR)$(PREFIX)/lib/sysusers.d/pacredir.conf $(INSTALL) -D -m0644 systemd/tmpfiles.conf $(DESTDIR)$(PREFIX)/lib/tmpfiles.d/pacredir.conf - $(INSTALL) -D -m0644 initcpio/hooks/pacredir $(DESTDIR)$(PREFIX)/lib/initcpio/hooks/pacredir - $(INSTALL) -D -m0644 initcpio/install/pacredir $(DESTDIR)$(PREFIX)/lib/initcpio/install/pacredir - $(INSTALL) -D -m0644 dhcpcd/80-pacredir $(DESTDIR)$(PREFIX)/lib/dhcpcd/dhcpcd-hooks/80-pacredir - $(INSTALL) -D -m0755 networkmanager/80-pacredir $(DESTDIR)$(PREFIX)/lib/NetworkManager/dispatcher.d/80-pacredir + $(INSTALL) -D -m0644 desktop/pacredir-status.desktop $(DESTDIR)$(PREFIX)/share/applications/pacredir-status.desktop + $(INSTALL) -D -m0644 logo.png $(DESTDIR)$(PREFIX)/share/pixmaps/pacredir.png + $(INSTALL) -D -m0644 dispatch/dhcpcd $(DESTDIR)$(PREFIX)/lib/dhcpcd/dhcpcd-hooks/80-pacredir + $(INSTALL) -D -m0755 dispatch/networkd $(DESTDIR)$(PREFIX)/lib/networkd-dispatcher/routable.d/80-pacredir + $(INSTALL) -D -m0755 dispatch/networkmanager $(DESTDIR)$(PREFIX)/lib/NetworkManager/dispatcher.d/80-pacredir install-doc: $(HTML) $(INSTALL) -d -m0755 $(DESTDIR)$(PREFIX)/share/doc/pacredir/ - $(INSTALL) -D -m0644 $(MARKDOWN) $(HTML) -t $(DESTDIR)$(PREFIX)/share/doc/pacredir/ - $(INSTALL) -d -m0755 $(DESTDIR)$(PREFIX)/share/doc/pacredir/FLOW/ - $(INSTALL) -D -m0644 $(wildcard FLOW/*) -t $(DESTDIR)$(PREFIX)/share/doc/pacredir/FLOW/ + $(INSTALL) -D -m0644 $(MARKDOWN) $(HTML) logo.svg -t $(DESTDIR)$(PREFIX)/share/doc/pacredir/ + $(INSTALL) -d -m0755 $(DESTDIR)$(PREFIX)/share/doc/pacredir/README.d/ + $(INSTALL) -D -m0644 $(wildcard README.d/*) -t $(DESTDIR)$(PREFIX)/share/doc/pacredir/README.d/ + $(INSTALL) -d -m0755 $(DESTDIR)$(PREFIX)/share/doc/pacredir/FLOW.d/ + $(INSTALL) -D -m0644 $(wildcard FLOW.d/*) -t $(DESTDIR)$(PREFIX)/share/doc/pacredir/FLOW.d/ + +install-avahi: compat/pacserve-announce.service + $(INSTALL) -D -m0644 compat/avahi.conf $(DESTDIR)$(PREFIX)/lib/systemd/system/pacserve.service.d/avahi.conf + $(INSTALL) -D -m0644 compat/pacserve-announce.service $(DESTDIR)$(PREFIX)/lib/systemd/system/pacserve-announce.service + $(INSTALL) -D -m0644 compat/02-pacredir-avahi-MulticastDNS-resolve.conf $(DESTDIR)/etc/systemd/resolved.conf.d/02-pacredir-avahi-MulticastDNS-resolve.conf clean: - $(RM) -f *.o *~ pacredir avahi/pacserve.service $(HTML) version.h + $(RM) -f *.o *~ pacredir $(SERVICES) $(HTML) favicon.png favicon.h version.h distclean: - $(RM) -f *.o *~ pacredir avahi/pacserve.service $(HTML) version.h config.h + $(RM) -f *.o *~ pacredir $(SERVICES) $(HTML) version.h config.h release: - git archive --format=tar.xz --prefix=pacredir-$(VERSION)/ $(VERSION) > pacredir-$(VERSION).tar.xz - gpg --armor --detach-sign --comment pacredir-$(VERSION).tar.xz pacredir-$(VERSION).tar.xz - git notes --ref=refs/notes/signatures/tar add -C $$(git archive --format=tar --prefix=pacredir-$(VERSION)/ $(VERSION) | gpg --armor --detach-sign --comment pacredir-$(VERSION).tar | git hash-object -w --stdin) $(VERSION) + git archive --format=tar.xz --prefix=pacredir-$(DISTVER)/ $(DISTVER) > pacredir-$(DISTVER).tar.xz + gpg --armor --detach-sign --comment pacredir-$(DISTVER).tar.xz pacredir-$(DISTVER).tar.xz + git notes --ref=refs/notes/signatures/tar add -C $$(git archive --format=tar --prefix=pacredir-$(DISTVER)/ $(DISTVER) | gpg --armor --detach-sign --comment pacredir-$(DISTVER).tar | git hash-object -w --stdin) $(DISTVER) diff --git a/README.d/resolvectl-query.png b/README.d/resolvectl-query.png Binary files differnew file mode 100644 index 0000000..7e1b96f --- /dev/null +++ b/README.d/resolvectl-query.png diff --git a/README.d/resolvectl-service.png b/README.d/resolvectl-service.png Binary files differnew file mode 100644 index 0000000..d4a6806 --- /dev/null +++ b/README.d/resolvectl-service.png diff --git a/README.d/status-page.png b/README.d/status-page.png Binary files differnew file mode 100644 index 0000000..00f6f14 --- /dev/null +++ b/README.d/status-page.png @@ -1,29 +1,45 @@ pacredir ======== -**pacredir - redirect pacman requests, assisted by avahi service discovery** +[](https://github.com/eworm-de/pacredir/stargazers) +[](https://github.com/eworm-de/pacredir/network) +[](https://github.com/eworm-de/pacredir/watchers) -By default every [Arch Linux](https://www.archlinux.org/) installation +**pacredir - redirect pacman requests, assisted by mDNS Service Discovery** + + + +By default every [Arch Linux ↗️](https://archlinux.org/) installation downloads its package files from online mirrors, transferring all the bits via WAN connection. But often other Arch systems may be around that already have the files available on local storage - just a fast LAN connection away. This is -where `pacredir` can help. It uses [Avahi](http://avahi.org/) to find -other instances and get the files there if available. +where `pacredir` can help. +It uses [mDNS Service Discovery ↗️](https://www.freedesktop.org/software/systemd/man/latest/systemd.dnssd.html) +by [systemd-resolved ↗️](https://www.freedesktop.org/software/systemd/man/latest/systemd-resolved) +to find other instances and redirect `pacman` there if available. + +*Use at your own risk*, pay attention to +[license and warranty](#license-and-warranty), and +[disclaimer on external links](#disclaimer-on-external-links)! Requirements ------------ To compile and run `pacredir` you need: -* [systemd](https://www.github.com/systemd/systemd) -* [avahi](https://avahi.org/) -* [libmicrohttpd](https://www.gnu.org/software/libmicrohttpd/) -* [curl](https://curl.haxx.se/) -* [iniparser](https://github.com/ndevilla/iniparser) -* [darkhttpd](https://unix4lyfe.org/darkhttpd/) -* [markdown](https://daringfireball.net/projects/markdown/) (HTML documentation) +* [systemd ↗️](https://www.github.com/systemd/systemd) +* [libmicrohttpd ↗️](https://www.gnu.org/software/libmicrohttpd/) +* [curl ↗️](https://curl.haxx.se/) +* [iniparser ↗️](https://github.com/ndevilla/iniparser) +* [darkhttpd ↗️](https://unix4lyfe.org/darkhttpd/) + +And these are build time or make dependencies: + +* [markdown ↗️](https://daringfireball.net/projects/markdown/) (HTML documentation) +* [resvg ↗️](https://github.com/linebender/resvg) (render the favicon) +* [oxipng ↗️](https://github.com/shssoichiro/oxipng) (optimize the favicon) `Arch Linux` installs development files for the packages by default, so no additional development packages are required. @@ -33,30 +49,60 @@ Build and install Building and installing is very easy. Just run: -> make + make followed by: -> make install + make install This will place an executable at `/usr/bin/pacredir`, documentation can be found in `/usr/share/doc/pacredir/`. Additionally systemd service files are installed to -`/usr/lib/systemd/system/` and avahi service files go to -`/etc/avahi/services/`. +`/usr/lib/systemd/system/`. + +You can not have `systemd-resolved` and the *avahi* daemon announce services +at the same time. If you really, *really* need the *avahi* daemon running +install the compatibility files: + + make install-avahi + +> ℹ️ **Info**: This is packaged as `pacredir` (and `pacredir-avahi` for the +> compatibility files) in [Arch Linux ↗️](https://archlinux.org/) and derived +> distributions. Install with: +> `pacman -Syu pacredir` Usage ----- -Enable systemd services `pacserve` and `pacredir`, open TCP -port `7078` and add the following line to your repository -definitions in `pacman.conf`: +Make sure *mDNS* is enabled in `systemd-resolved`, and also make sure it +is enabled for all interface you want to use this with. Run `resolvectl` +to get an overview. + +Then enable and start systemd services `pacserve` and `pacredir`, allow +TCP port `7078` in your firewall and add the following line to your +repository definitions in `pacman.conf`: -> Include = /etc/pacman.d/pacredir + Include = /etc/pacman.d/pacredir To get a better idea what happens in the background have a look at [the request flow chart](FLOW.md). +### Status page + +A simple status page is available when `pacredir` is running. Just point +your browser to [your local instance](http://localhost:7077/). + + + +The instance is listening on `localhost` only. To access it from a different +host you need to proxy (for example reverse proxy with `nginx`) or tunnel +it. The latter can be used ad hoc, for example with `ssh` local forwarding: + + ssh -L 17077:localhost:7077 host + +Then point your browser to `http://localhost:17077/`. A desktop file for +that url is installed, so your desktop environment has a shortcut. + ### Databases from cache server By default databases are not fetched from cache servers. To make that @@ -66,8 +112,7 @@ line. Do not worry if `pacman` reports the following after the change: -> error: failed retrieving file 'core.db' from 127.0.0.1:7077 : The -> requested URL returned error: 404 Not Found + error: failed retrieving file 'core.db' from 127.0.0.1:7077 : The requested URL returned error: 404 Not Found This is ok, it just tells `pacman` that `pacredir` could not find a file and downloading it from an official server is required. @@ -85,6 +130,24 @@ broken and/or malicious files to you, but this is by design. So make sure `pacman` is configured to check signatures! It will then detect if anything goes wrong. +Querying `systemd-resolved` +--------------------------- + +You may want to query `systemd-resolved` directly to verify the results. +This will list all instances: + + resolvectl query --legend=false --protocol=mdns --type=PTR --synthesize=false --zone=false '_pacserve._tcp.local' + + + +(The escape sequence `\032` represents a space.) + +For details on the services you can query each: + + resolvectl service --legend=false 'pacserve on host._pacserve._tcp.local' + + + License and warranty -------------------- @@ -98,6 +161,21 @@ but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the [GNU General Public License](COPYING.md) for more details. +Disclaimer on external links +---------------------------- + +Our website contains links to the websites of third parties ("external +links"). As the content of these websites is not under our control, we +cannot assume any liability for such external content. In all cases, the +provider of information of the linked websites is liable for the content +and accuracy of the information provided. At the point in time when the +links were placed, no infringements of the law were recognisable to us. +As soon as an infringement of the law becomes known to us, we will +immediately remove the link in question. + +> 💡️ **Hint**: All external links are marked with an arrow pointing +> diagonally in an up-right (or north-east) direction (↗️). + ### Upstream URL: @@ -106,3 +184,6 @@ URL: Mirror: [eworm.de](https://git.eworm.de/cgit.cgi/pacredir/about/) [GitLab.com](https://gitlab.com/eworm-de/pacredir#pacredir) + +--- +[⬆️ Go back to top](#top) diff --git a/avahi/pacserve.service.in b/avahi/pacserve.service.in deleted file mode 100644 index 4a39e6d..0000000 --- a/avahi/pacserve.service.in +++ /dev/null @@ -1,15 +0,0 @@ -<?xml version="1.0" standalone='no'?><!--*-nxml-*--> -<!DOCTYPE service-group SYSTEM "avahi-service.dtd"> - -<!-- See avahi.service(5) for more information about this configuration file --> - -<service-group> - - <name replace-wildcards="yes">%h</name> - - <service> - <type>_pacserve_%ID%_%ARCH%._tcp</type> - <port>7078</port> - </service> - -</service-group> diff --git a/compat/02-pacredir-avahi-MulticastDNS-resolve.conf b/compat/02-pacredir-avahi-MulticastDNS-resolve.conf new file mode 100644 index 0000000..314b8f7 --- /dev/null +++ b/compat/02-pacredir-avahi-MulticastDNS-resolve.conf @@ -0,0 +1,9 @@ +# This file is shipped by pacredir to enable multicast DNS in +# systemd-resolved globally. +# +# For Avahi compatibility the value is overwritten from 'yes' +# (see 01-pacredir-MulticastDNS-yes.conf) to 'resolve'. This +# allows avahi-daemon to announce the pacserve service. + +[Resolve] +MulticastDNS=resolve diff --git a/compat/avahi.conf b/compat/avahi.conf new file mode 100644 index 0000000..a486d74 --- /dev/null +++ b/compat/avahi.conf @@ -0,0 +1,6 @@ +[Unit] +Upholds=pacserve-announce.service + +[Service] +ExecStartPost= +ExecStopPost= diff --git a/compat/pacserve-announce.service.in b/compat/pacserve-announce.service.in new file mode 100644 index 0000000..e425f68 --- /dev/null +++ b/compat/pacserve-announce.service.in @@ -0,0 +1,23 @@ +# (C) 2013-2025 by Christian Hesse <mail@eworm.de> +# Markus Weippert <markus@gekmihesg.de> +# +# This program 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. + +[Unit] +Description=Announce pacman database files and package archives +Documentation=https://pacredir.eworm.de/ +After=avahi-daemon.service pacserve.service +BindsTo=pacserve.service +Requires=avahi-daemon.service + +[Service] +EnvironmentFile=/etc/pacserve.conf +ExecStart=/usr/bin/avahi-publish -s "pacserve on %l" _pacserve._tcp ${PORT} id=%ID% arch=%ARCH% +DynamicUser=on +ProtectSystem=full +ProtectHome=on +PrivateDevices=on +NoNewPrivileges=on diff --git a/config.def.h b/config.def.h index 5fd7c13..b2e29fe 100644 --- a/config.def.h +++ b/config.def.h @@ -1,5 +1,5 @@ /* - * (C) 2013-2024 by Christian Hesse <mail@eworm.de> + * (C) 2013-2025 by Christian Hesse <mail@eworm.de> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,22 +23,17 @@ #define DROP_PRIV_UID 65534 #define DROP_PRIV_GID 65534 -/* website url */ -#define WEBSITE "https://github.com/eworm-de/pacredir#pacredir" - -/* This is used for default documents. Usually you will not see this anyway. */ -#define PAGE307 "<html><head><title>307 temporary redirect</title>" \ - "</head><body>307 temporary redirect: " \ - "<a href=\"%s\">%s</a></body></html>" -#define PAGE404 "<html><head><title>404 Not Found</title>" \ - "</head><body>404 Not Found: %s</body></html>" +/* website & url */ +#define WEBSITE "pacredir.eworm.de" +#define WEBURL "https://" WEBSITE "/" /* the ports pacredir and pacserve listen to */ #define PORT_PACREDIR 7077 #define PORT_PACSERVE 7078 -/* avahi service names */ -#define PACSERVE "_pacserve_" ID "_" ARCH "._tcp" +/* mDNS service name */ +#define PACSERVE "_pacserve._tcp" +#define MDNS_DOMAIN "local" /* path to the config file */ #define CONFIGFILE "/etc/pacredir.conf" diff --git a/desktop/pacredir-status.desktop b/desktop/pacredir-status.desktop new file mode 100644 index 0000000..6a30479 --- /dev/null +++ b/desktop/pacredir-status.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Type=Link +Version=1.0 +Name=pacredir status +GenericName=Status and statistics for pacredir +Comment=Show status and statistics for pacredir +Icon=/usr/share/pixmaps/pacredir.png +URL=http://localhost:7077/ +Categories=System;PackageManager +Keywords=system;PackageManager diff --git a/dhcpcd/80-pacredir b/dispatch/dhcpcd index 6e8561b..b50cb87 100644 --- a/dhcpcd/80-pacredir +++ b/dispatch/dhcpcd @@ -3,9 +3,7 @@ case "${reason}" in BOUND|REBIND|REBOOT|RENEW|TIMEOUT|STATIC) # inform pacredir about reestablished connection - if systemctl is-active -q pacredir.service; then - systemctl reload pacredir.service - fi + systemctl try-reload-or-restart pacredir.service ;; PREINIT|EXPIRE|INFORM|FAIL|IPV4LL|NAK|NOCARRIER|RELEASE|STOP) # do nothing here... diff --git a/dispatch/networkd b/dispatch/networkd new file mode 100755 index 0000000..591aecd --- /dev/null +++ b/dispatch/networkd @@ -0,0 +1,3 @@ +#!/bin/sh + +systemctl try-reload-or-restart pacredir.service diff --git a/dispatch/networkmanager b/dispatch/networkmanager new file mode 100755 index 0000000..146b585 --- /dev/null +++ b/dispatch/networkmanager @@ -0,0 +1,5 @@ +#!/bin/sh + +if [ "${2}" = "up" ]; then + systemctl try-reload-or-restart pacredir.service +fi diff --git a/etc/01-pacredir-MulticastDNS-yes.conf b/etc/01-pacredir-MulticastDNS-yes.conf new file mode 100644 index 0000000..49ab0a7 --- /dev/null +++ b/etc/01-pacredir-MulticastDNS-yes.conf @@ -0,0 +1,10 @@ +# This file is shipped by pacredir to enable multicast DNS in +# systemd-resolved globally. That functionality is required, though +# you may want to enable it on specific interfaces only. +# See systemd-resolved(8) and resolved.conf(5) for details. +# +# If you need avahi-daemon running on your system install +# optional package 'pacredir-avahi' for compatibility. + +[Resolve] +MulticastDNS=yes diff --git a/pacredir.conf b/etc/pacredir.conf index e0d86ab..ab144f0 100644 --- a/pacredir.conf +++ b/etc/pacredir.conf @@ -10,7 +10,7 @@ max threads = 0 #max threads = 32 -# Some people like to run avahi on network interfaces with low bandwidth or +# Some people like to run mDNS on network interfaces with low bandwidth or # high cost, for example to use 'Bonjour' (Link-Local Messaging) on it. # Add these interfaces here to ignore them by pacredir. Just give multiple # interface if desired, separated by space, comma or semicolon. @@ -19,13 +19,7 @@ max threads = 0 #ignore interfaces = openvpn #ignore interfaces = tap0 tun0 openvpn -# Specify to use IPv4, IPv6 or both. This does *not* limit the protocol for -# requests but what discovered services from avahi are handled. -protocol = IPv4 -#protocol = IPv6 -#protocol = both - -# You may want to add hosts that do not announce their services via avahi or +# You may want to add hosts that do not announce their services via mDNS or # are connected to a different network segment. Add them here. IPv6 addresses # have to be enclosed in square brackets. # Please note that pacserve hosts depend on the peers' architecture! diff --git a/etc/pacserve.conf b/etc/pacserve.conf new file mode 100644 index 0000000..6a0a279 --- /dev/null +++ b/etc/pacserve.conf @@ -0,0 +1,4 @@ +# pacserve configuration file + +# Port to listen on +PORT=7078 @@ -0,0 +1,101 @@ +/* + * (C) 2013-2025 by Christian Hesse <mail@eworm.de> + * + * This program 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. + * + * This program 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 this program. If not, see <https://www.gnu.org/licenses/>. + * + */ + +#ifndef _HTML_H +#define _HTML_H + +/* This is used for default documents. Usually you will not see this anyway. */ +#define PAGE307 \ + "<html><head><title>307 temporary redirect</title>" \ + "</head><body>307 temporary redirect: " \ + "<a href=\"%s\">%s</a></body></html>" +#define PAGE404 \ + "<html><head><title>404 Not Found</title>" \ + "</head><body>404 Not Found: %s</body></html>" + +/* status page */ +#define CIRCLE_GREEN "🟢" +#define CIRCLE_YELLOW "🟡" +#define CIRCLE_ORANGE "🟠" +#define CIRCLE_RED "🔴" +#define CIRCLE_BLUE "🔵" + +#define STATUS_HEAD \ + "<!DOCTYPE html><html lang=\"en\">" \ + "<head><title>pacredir status</title>" \ + "<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">" \ + "<meta http-equiv=\"refresh\" content=\"30\">" \ + "<style>body { font-family: sans-serif; } " \ + "h1 { padding-left: 36px; background-image: url(\"/favicon.png\"); background-repeat: no-repeat; } " \ + "h2 a { color: black; } " \ + "a { text-decoration: none; } " \ + "a:hover { text-decoration: underline; } " \ + "th { background: #efefef; } " \ + "td { text-align: center; padding: 1px 5px; } " \ + "tr:nth-child(even) { background: #dfdfdf; } " \ + "tr:nth-child(odd) { background: #efefef; } " \ + "tr:hover { background: #dfefef; }" \ + "tr.grey { color: grey; }</style>" \ + "<link rel=\"icon\" href=\"/favicon.png \" type=\"image/png\">" \ + "</head><body><h1>pacredir status</h1>" \ + "<p>This is <code>pacredir</code> version <i>" VERSION "</i> running on <i>%s</i>. " \ + "Visit <a href=\"" WEBURL "\">" WEBSITE "</a> for documentation.</p>" \ + "<table>" \ + "<tr><td>Distribution:</td><td><b>" ID "</b></td></tr>" \ + "<tr><td>Architecture:</td><td><b>" ARCH "</b></td></tr>" \ + "<tr><td>Redirects:</td><td><b>%d</b></td></tr>" \ + "<tr><td>Not found:</td><td><b>%d</b></td></tr>" \ + "<tr><td>Over all:</td><td>%s</td></tr>" \ + "</table>" + +#define STATUS_INT_HEAD \ + "<h2 id=\"ignored-interfaces\"><a href=\"#ignored-interfaces\">Ignored interfaces</a></h2>" \ + "<table><tr><th>interface</th><th>link</th></tr>" +#define STATUS_INT_ONE \ + "<tr><td>%s</td><td>%d</td></tr>" +#define STATUS_INT_ONE_NA \ + "<tr class=\"grey\"><td>%s</td><td>-</td></tr>" +#define STATUS_INT_NONE \ + "<tr><td colspan=2>(none)</td></tr>" +#define STATUS_INT_FOOT \ + "</table>" + +#define STATUS_HOST_HEAD \ + "<h2 id=\"hosts\"><a href=\"#hosts\">Hosts</a></h2>" \ + "<table><tr>" \ + "<th>host</th>" \ + "<th>port</th>" \ + "<th colspan=2>state</th>" \ + "<th colspan=2>finds</th>" \ + "<th colspan=2>bad</th></tr>" +#define STATUS_HOST_ONE \ + "<tr%s>" \ + "<td>%s</td>" \ + "<td>%d</td>" \ + "<td>%s</td><td>%s</td>" \ + "<td>%s</td><td>%d</td>" \ + "<td>%s</td><td>%d</td></tr>" +#define STATUS_HOST_NONE \ + "<tr><td colspan=8>(none)</td></tr>" +#define STATUS_HOST_FOOT \ + "</table>" + +#define STATUS_FOOT \ + "</body></html>" + +#endif /* _HTML_H */ diff --git a/initcpio/hooks/pacredir b/initcpio/hooks/pacredir deleted file mode 100644 index c73bb8e..0000000 --- a/initcpio/hooks/pacredir +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh - -run_latehook() { - local newroot="/new_root/" - - if ! grep -q '^pacserve hosts' ${newroot}/etc/pacredir.conf; then - case $(uname -m) in - x86_64) - if [[ -n "${pacserve_x86_64}" ]]; then - msg ":: Adding pacserve host '${pacserve_x86_64}' to pacredir.conf..." - echo "pacserve hosts = ${pacserve_x86_64}" >> ${newroot}/etc/pacredir.conf - fi - ;; - i686) - if [[ -n "${pacserve_i686}" ]]; then - msg ":: Adding pacserve host '${pacserve_i686}' to pacredir.conf..." - echo "pacserve hosts = ${pacserve_i686}" >> ${newroot}/etc/pacredir.conf - fi - ;; - esac - fi -} diff --git a/initcpio/install/pacredir b/initcpio/install/pacredir deleted file mode 100644 index 30d248f..0000000 --- a/initcpio/install/pacredir +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -build() { - add_runscript -} - -help() { - echo 'This hook adds peers to pacredir.conf from inside initramfs.' - echo 'Useless for installed systems, but can be handy with' - echo 'no-persistent configurations.' -} diff --git a/logo.png b/logo.png Binary files differnew file mode 100644 index 0000000..95e7536 --- /dev/null +++ b/logo.png diff --git a/logo.svg b/logo.svg new file mode 100644 index 0000000..6626c12 --- /dev/null +++ b/logo.svg @@ -0,0 +1,98 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --> + +<svg + fill="#000000" + width="96" + height="96" + viewBox="0 0 2.88 2.88" + version="1.1" + id="svg1" + sodipodi:docname="logo.svg" + inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)" + inkscape:export-filename="pacman-redir.png" + inkscape:export-xdpi="192" + inkscape:export-ydpi="192" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1" /> + <sodipodi:namedview + id="namedview1" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:showpageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" + showgrid="false" + inkscape:zoom="8.8561223" + inkscape:cx="47.932942" + inkscape:cy="45.956908" + inkscape:window-width="1920" + inkscape:window-height="1047" + inkscape:window-x="0" + inkscape:window-y="33" + inkscape:window-maximized="1" + inkscape:current-layer="svg1" /> + <g + id="g2"> + <rect + style="fill:#ffffff;stroke-width:0.06;stroke-linecap:round;stroke-linejoin:round" + id="rect1" + x="0" + y="0" + height="2.8800001" + width="2.8800001" + rx="0.45000005" + ry="0.45000005" /> + <path + style="fill:#ffcc00;fill-opacity:1;stroke:#000000;stroke-width:0.18;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none" + id="path2" + sodipodi:type="arc" + sodipodi:cx="1.35" + sodipodi:cy="1.3500063" + sodipodi:rx="1.17" + sodipodi:ry="1.17" + sodipodi:start="0.61086524" + sodipodi:end="5.6723201" + sodipodi:arc-type="slice" + d="M 2.3084079,2.0210907 A 1.17,1.17 0 0 1 0.99817424,2.4658551 1.17,1.17 0 0 1 0.18000007,1.3500063 1.17,1.17 0 0 1 0.99817428,0.23415754 1.17,1.17 0 0 1 2.3084079,0.67892197 L 1.35,1.3500063 Z" /> + <circle + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.06;stroke-linecap:round;stroke-linejoin:round" + id="path3" + cx="1.08" + cy="0.81000006" + r="0.27000001" /> + <g + id="g1"> + <rect + style="fill:#dd55ff;fill-opacity:1;stroke:#000000;stroke-width:0.18;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1;stroke-dasharray:none" + id="rect4" + width="1.2335274" + height="0.60436016" + x="1.4664726" + y="1.8063096" + ry="0.30218008" /> + <path + sodipodi:type="star" + style="fill:#dd55ff;fill-opacity:1;stroke:#000000;stroke-width:0.06;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1;stroke-dasharray:none" + id="path4" + inkscape:flatsided="true" + sodipodi:sides="3" + sodipodi:cx="-0.16725793" + sodipodi:cy="0.41981158" + sodipodi:r1="0.22600283" + sodipodi:r2="0.2553075" + sodipodi:arg1="1.017775" + sodipodi:arg2="2.0649725" + inkscape:rounded="0.1" + inkscape:randomized="0" + d="m -0.04854756,0.61212674 c -0.03330996,0.0205612 -0.34346381,-0.14653865 -0.34461539,-0.18566654 -0.001152,-0.0391279 0.29863811,-0.22417907 0.33309965,-0.20561241 0.03446154,0.0185667 0.04482571,0.37071771 0.01151574,0.39127895 z" + transform="matrix(3.0000001,0,0,3.0000001,2.036195,0.85837623)" /> + </g> + </g> +</svg> diff --git a/networkmanager/80-pacredir b/networkmanager/80-pacredir deleted file mode 100755 index 32114ae..0000000 --- a/networkmanager/80-pacredir +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh - -if [ "${2}" == "up" ]; then - if systemctl is-active -q pacredir.service; then - systemctl reload pacredir.service - fi -fi @@ -1,5 +1,5 @@ /* - * (C) 2013-2024 by Christian Hesse <mail@eworm.de> + * (C) 2013-2025 by Christian Hesse <mail@eworm.de> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -32,251 +32,454 @@ const static struct option options_long[] = { struct hosts * hosts = NULL; struct ignore_interfaces * ignore_interfaces = NULL; int max_threads = 0; -static AvahiSimplePoll *simple_poll = NULL; -uint8_t verbose = 0; +uint8_t quit = 0, update = 0, verbose = 0; unsigned int count_redirect = 0, count_not_found = 0; /*** write_log ***/ -int write_log(FILE *stream, const char *format, ...) { +static int write_log(FILE *stream, const char *format, ...) { va_list args; - va_start(args, format); + va_start(args, format); vfprintf(stream, format, args); + va_end(args); fflush(stream); return EXIT_SUCCESS; } -/*** get_fqdn ***/ -char * get_fqdn(const char * hostname, const char * domainname) { - char * name; - - name = malloc(strlen(hostname) + strlen(domainname) + 2 /* '.' and null char */); - sprintf(name, "%s.%s", hostname, domainname); - - return name; -} - /*** get_url ***/ -char * get_url(const char * hostname, AvahiProtocol proto, const char * address, const uint16_t port, const uint8_t dbfile, const char * uri) { - const char * host, * dir; +static char * get_url(const char * hostname, const uint16_t port, const uint8_t dbfile, const char * uri) { + const char * dir; char * url; - host = *address ? address : hostname; - dir = dbfile ? "db" : "pkg"; - - url = malloc(10 /* static chars of an url & null char */ - + strlen(host) + url = malloc(11 /* static chars of an url & null char */ + + strlen(hostname) + 5 /* max strlen of decimal 16bit value */ - + 2 /* square brackets for IPv6 address */ - + 4 /* extra dir */ + + strlen(dir) + strlen(uri)); - if (*address != 0 && proto == AVAHI_PROTO_INET6) - sprintf(url, "http://[%s]:%d/%s/%s", address, port, dir, uri); - else - sprintf(url, "http://%s:%d/%s/%s", host, port, dir, uri); + sprintf(url, "http://%s:%d/%s/%s", hostname, port, dir, uri); return url; } -/*** add_host ***/ -int add_host(const char * host, AvahiProtocol proto, const char * address, const uint16_t port, const char * type) { - struct hosts * tmphosts = hosts; - struct request request; +/*** update_interfaces ***/ +static void update_interfaces(void) { + struct ignore_interfaces *ignore_interfaces_ptr = ignore_interfaces; - while (tmphosts->host != NULL) { - if (strcmp(tmphosts->host, host) == 0 && tmphosts->proto == proto) { - /* host already exists */ - if (verbose > 0) - write_log(stdout, "Updating service %s (port %d) on host %s (%s)\n", - type, port, host, avahi_proto_to_string(proto)); - goto update; - } - tmphosts = tmphosts->next; + while (ignore_interfaces_ptr->interface != NULL) { + ignore_interfaces_ptr->ifindex = if_nametoindex(ignore_interfaces_ptr->interface); + ignore_interfaces_ptr = ignore_interfaces_ptr->next; } +} - /* host not found, adding a new one */ - if (verbose > 0) - write_log(stdout, "Adding host %s (%s) with service %s (port %d)\n", - host, avahi_proto_to_string(proto), type, port); - - tmphosts->host = strdup(host); - tmphosts->proto = AVAHI_PROTO_UNSPEC; - *tmphosts->address = 0; +/*** get_name ***/ +static size_t get_name(const uint8_t* rr_ptr, char* name) { + uint8_t dot = 0; + char *name_ptr = name; - tmphosts->port = 0; - tmphosts->online = 0; - tmphosts->badtime = 0; - tmphosts->badcount = 0; + for (;;) { + if (*rr_ptr == 0) + return (strlen(name) + 2); + if (dot) + *(name_ptr++) = '.'; + else + dot++; + memcpy(name_ptr, rr_ptr + 1, *rr_ptr + 1); + name_ptr += *rr_ptr; + rr_ptr += *rr_ptr + 1; + } +} - tmphosts->next = malloc(sizeof(struct hosts)); - tmphosts->next->host = NULL; - tmphosts->next->next = NULL; +/*** process_reply_record ***/ +static char* process_reply_record(const void *rr, size_t sz) { + uint16_t class, type, rdlength; + const uint8_t *rr_ptr = rr; + char *name; + uint32_t ttl; -update: - tmphosts->proto = proto; - if (address != NULL) - memcpy(tmphosts->address, address, AVAHI_ADDRESS_STR_MAX); + rr_ptr += strlen((char*)rr_ptr) + 1; + memcpy(&type, rr_ptr, sizeof(uint16_t)); + rr_ptr += sizeof(uint16_t); + memcpy(&class, rr_ptr, sizeof(uint16_t)); + rr_ptr += sizeof(uint16_t); + memcpy(&ttl, rr_ptr, sizeof(uint32_t)); + rr_ptr += sizeof(uint32_t); + memcpy(&rdlength, rr_ptr, sizeof(uint16_t)); + rr_ptr += sizeof(uint16_t); + assert(be16toh(type) == DNS_TYPE_PTR); + assert(be16toh(class) == DNS_CLASS_IN); - tmphosts->online = 1; - tmphosts->port = port; + name = malloc(strlen((char*)rr_ptr) + 1); + rr_ptr += get_name(rr_ptr, name); - /* do a first request and let get_http_code() set the bad status */ - request.host = tmphosts; - request.url = get_url(request.host->host, request.host->proto, request.host->address, request.host->port, 0, ""); - request.http_code = 0; - request.last_modified = 0; - get_http_code(&request); - free(request.url); + assert(rr_ptr == (const uint8_t*) rr + sz); - return EXIT_SUCCESS; + return name; } -/*** remove_host ***/ -int remove_host(const char * host, AvahiProtocol proto, const char * type) { - struct hosts * tmphosts = hosts; +/*** update_hosts ***/ +static void update_hosts(void) { + struct if_nameindex *if_nidxs, *intf; + struct hosts *hosts_ptr = hosts; + sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus_message *reply = NULL; + sd_bus *bus = NULL; + int r, sock; - while (tmphosts->host != NULL) { - if (strcmp(tmphosts->host, host) == 0 && tmphosts->proto == proto) { - if (verbose > 0) - write_log(stdout, "Marking service %s on host %s (%s) offline\n", - type, host, avahi_proto_to_string(proto)); - tmphosts->online = 0; + /* set 'present' to 0, so we later know which hosts were available, and which were not */ + while (hosts_ptr->host != NULL) { + hosts_ptr->present = 0; + hosts_ptr = hosts_ptr->next; + } + + r = sd_bus_open_system(&bus); + if (r < 0) { + write_log(stderr, "Failed to open system bus: %s\n", strerror(-r)); + goto fast_finish; + } + + r = sd_bus_call_method(bus, "org.freedesktop.resolve1", "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", "ResolveRecord", &error, + &reply, "isqqt", 0 /* any */, PACSERVE "." MDNS_DOMAIN, + DNS_CLASS_IN, DNS_TYPE_PTR, SD_RESOLVED_NO_SYNTHESIZE|SD_RESOLVED_NO_ZONE); + if (r < 0) { + if (verbose > 0) + write_log(stderr, "Failed to resolve record: %s\n", error.message); + sd_bus_error_free(&error); + goto finish; + } + + /* On empty cache systemd-resolved returns just one record on first query, + happened above. Similar delays are seen for new hosts. + Wait a moment, call again for full update, then goto finish. + TODO: Drop when continuous mDNS querying becomes available! */ + usleep(250000); + + if ((if_nidxs = if_nameindex()) == NULL) { + write_log(stderr, "Failed to get list of interfaces.\n"); + goto finish; + } + + if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + write_log(stderr, "Failed to open control socket.\n"); + goto finish; + } + + for (intf = if_nidxs; intf->if_index != 0 || intf->if_name != NULL; intf++) { + struct ifreq ifr; + struct ignore_interfaces *ignore_interfaces_ptr = ignore_interfaces; + uint8_t ignore = 0; + + memset(&ifr, 0, sizeof(struct ifreq)); + strncpy(ifr.ifr_name, intf->if_name, IFNAMSIZ); + ifr.ifr_name[IFNAMSIZ-1] = 0; + + if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) { + write_log(stderr, "Failed to get flags for interface %s.\n", intf->if_name); + continue; + } + + if ((ifr.ifr_flags & IFF_UP) == 0) + continue; + + if ((ifr.ifr_flags & IFF_LOOPBACK) > 0) + continue; + + if ((ifr.ifr_flags & IFF_RUNNING) == 0) + continue; + + while (ignore_interfaces_ptr->interface != NULL) { + if (ignore_interfaces_ptr->ifindex == intf->if_index) { + ignore++; + break; + } + + ignore_interfaces_ptr = ignore_interfaces_ptr->next; + } + + if (!ignore) + update_hosts_on_interface(bus, intf->if_index); + + if (quit) break; + } + + close(sock); + if_freenameindex(if_nidxs); + +finish: + /* mark hosts offline that did not show up in query */ + hosts_ptr = hosts; + while (hosts_ptr->host != NULL) { + if (hosts_ptr->mdns == 1 && hosts_ptr->online == 1 && hosts_ptr->present == 0) { + if (verbose > 0) + write_log(stdout, "Marking host %s offline\n", hosts_ptr->host); + hosts_ptr->online = 0; } - tmphosts = tmphosts->next; + hosts_ptr = hosts_ptr->next; } - return EXIT_SUCCESS; +fast_finish: + sd_bus_message_unref(reply); + sd_bus_flush_close_unref(bus); } -/*** resolve_callback *** - * Called whenever a service has been resolved successfully or timed out */ -static void resolve_callback(AvahiServiceResolver *r, - AvahiIfIndex interface, - AvahiProtocol protocol, - AvahiResolverEvent event, - const char *name, - const char *type, - const char *domain, - const char *host, - const AvahiAddress *address, - uint16_t port, - AvahiStringList *txt, - AvahiLookupResultFlags flags, - void* userdata) { - char ipaddress[AVAHI_ADDRESS_STR_MAX]; - char intname[IFNAMSIZ]; +/*** update_hosts_on_interface ***/ +static void update_hosts_on_interface(sd_bus *bus, const unsigned int if_index) { + sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus_message *reply_record = NULL; + uint64_t flags; + int r; - assert(r); + r = sd_bus_call_method(bus, "org.freedesktop.resolve1", "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", "ResolveRecord", &error, + &reply_record, "isqqt", if_index, PACSERVE "." MDNS_DOMAIN, + DNS_CLASS_IN, DNS_TYPE_PTR, SD_RESOLVED_NO_SYNTHESIZE|SD_RESOLVED_NO_ZONE); + if (r < 0) { + if (verbose > 0) + write_log(stderr, "Failed to resolve record: %s\n", error.message); + sd_bus_error_free(&error); + goto finish; + } - if_indextoname(interface, intname); + r = sd_bus_message_enter_container(reply_record, 'a', "(iqqay)"); + if (r < 0) + goto parse_failure_record; - switch (event) { - case AVAHI_RESOLVER_FAILURE: - write_log(stderr, "Failed to resolve service '%s' of type '%s' in domain '%s': %s\n", - name, type, domain, avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r)))); + for (;;) { + int ifindex; + uint16_t class, type, port; + const void *data; + size_t length; + char *peer; + const char *canonical, *discard; + uint8_t match = 0, ignore = 0; + + r = sd_bus_message_enter_container(reply_record, 'r', "iqqay"); + if (r < 0) + goto parse_failure_record; + if (r == 0) /* Reached end of array */ break; + r = sd_bus_message_read(reply_record, "iqq", &ifindex, &class, &type); + if (r < 0) + goto parse_failure_record; + r = sd_bus_message_read_array(reply_record, 'y', &data, &length); + if (r < 0) + goto parse_failure_record; + r = sd_bus_message_exit_container(reply_record); + if (r < 0) + goto parse_failure_record; - case AVAHI_RESOLVER_FOUND: - avahi_address_snprint(ipaddress, AVAHI_ADDRESS_STR_MAX, address); + /* process the data received */ + peer = process_reply_record(data, length); + sd_bus_message *reply_service = NULL; + /* service START */ + r = sd_bus_call_method(bus, "org.freedesktop.resolve1", "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", "ResolveService", &error, + &reply_service, "isssit", 0 /* any */, "", "", peer, AF_UNSPEC, UINT64_C(0)); + if (r < 0) { if (verbose > 0) - write_log(stdout, "Found service %s on host %s (%s) on interface %s\n", - type, host, ipaddress, intname); + write_log(stderr, "Failed to resolve service '%s': %s\n", peer, error.message); + sd_bus_error_free(&error); + goto finish_service; + } - add_host(host, protocol, ipaddress, PORT_PACSERVE, type); - break; - } + r = sd_bus_message_enter_container(reply_service, 'a', "(qqqsa(iiay)s)"); + if (r < 0) + goto parse_failure_service; - avahi_service_resolver_free(r); -} + for (;;) { + uint16_t priority, weight; + const char *hostname; -/*** browse_callback *** - * Called whenever a new services becomes available on the LAN or is removed from the LAN */ -static void browse_callback(AvahiServiceBrowser *b, - AvahiIfIndex interface, - AvahiProtocol protocol, - AvahiBrowserEvent event, - const char *name, - const char *type, - const char *domain, - AvahiLookupResultFlags flags, - void* userdata) { - char * host; - char intname[IFNAMSIZ]; - struct ignore_interfaces * tmp_ignore_interfaces = ignore_interfaces; - AvahiClient * c; + r = sd_bus_message_enter_container(reply_service, 'r', "qqqsa(iiay)s"); + if (r < 0) + goto parse_failure_service; + if (r == 0) /* Reached end of array */ + break; + r = sd_bus_message_read(reply_service, "qqqs", &priority, &weight, &port, &hostname); + if (r < 0) + goto parse_failure_service; - assert(b); + r = sd_bus_message_enter_container(reply_service, 'a', "(iiay)"); + if (r < 0) + goto parse_failure_service; - c = userdata; - if_indextoname(interface, intname); + for (;;) { + int ifindex, family; + const void *data; + size_t length; - switch (event) { - case AVAHI_BROWSER_FAILURE: - write_log(stderr, "Failed to browse: %s\n", avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(b)))); - avahi_simple_poll_quit(simple_poll); - return; + r = sd_bus_message_enter_container(reply_service, 'r', "iiay"); + if (r < 0) + goto parse_failure_service; + if (r == 0) /* Reached end of array */ + break; + r = sd_bus_message_read(reply_service, "ii", &ifindex, &family); + if (r < 0) + goto parse_failure_service; + r = sd_bus_message_read_array(reply_service, 'y', &data, &length); + if (r < 0) + goto parse_failure_service; + r = sd_bus_message_exit_container(reply_service); + if (r < 0) + goto parse_failure_service; + } + r = sd_bus_message_exit_container(reply_service); + if (r < 0) + goto parse_failure_service; - case AVAHI_BROWSER_NEW: - host = get_fqdn(name, domain); + r = sd_bus_message_read(reply_service, "s", &canonical); + if (r < 0) + goto parse_failure_service; + r = sd_bus_message_exit_container(reply_service); + if (r < 0) + goto parse_failure_service; + } - if (flags & AVAHI_LOOKUP_RESULT_LOCAL) - goto out; + r = sd_bus_message_exit_container(reply_service); + if (r < 0) + goto parse_failure_service; + r = sd_bus_message_enter_container(reply_service, 'a', "ay"); + if (r < 0) + goto parse_failure_service; - /* check whether to ignore the interface */ - while (tmp_ignore_interfaces->next != NULL) { - if (strcmp(intname, tmp_ignore_interfaces->interface) == 0) { - if (verbose > 0) - write_log(stdout, "Ignoring service %s on host %s on interface %s\n", - type, host, intname); - goto out; - } - tmp_ignore_interfaces = tmp_ignore_interfaces->next; - } + for(;;) { + const void *txt_data; + size_t txt_len; - if ((avahi_service_resolver_new(c, interface, protocol, name, type, domain, protocol, 0, resolve_callback, c)) == NULL) - write_log(stderr, "Failed to create resolver for service '%s' of type '%s' in domain '%s': %s\n", - name, type, domain, avahi_strerror(avahi_client_errno(c))); -out: - free(host); + r = sd_bus_message_read_array(reply_service, 'y', &txt_data, &txt_len); + if (r < 0) + goto parse_failure_service; + if (r == 0) /* Reached end of array */ + break; - break; + /* does the TXT data match our architecture (arch) or distribution (id)? */ + if (strncmp((char*)txt_data, "arch=" ARCH, txt_len) == 0) + match |= DNS_SRV_TXT_MATCH_ARCH; + if (strncmp((char*)txt_data, "id=" ID, txt_len) == 0) + match |= DNS_SRV_TXT_MATCH_ID; + } + + r = sd_bus_message_exit_container(reply_service); + if (r < 0) + goto parse_failure_service; + + r = sd_bus_message_read(reply_service, "s", &discard); + if (r < 0) + goto parse_failure_service; + r = sd_bus_message_read(reply_service, "s", &discard); + if (r < 0) + goto parse_failure_service; + r = sd_bus_message_read(reply_service, "s", &discard); + if (r < 0) + goto parse_failure_service; - case AVAHI_BROWSER_REMOVE: - host = get_fqdn(name, domain); + r = sd_bus_message_read(reply_service, "t", &flags); + if (r < 0) + goto parse_failure_service; + + if (ignore > 0) { + if (verbose > 0) + write_log(stdout, "Host %s is on an ignored interface.\n", canonical); + goto finish_service; + } + if (match < DNS_SRV_TXT_MATCH_ALL) { if (verbose > 0) - write_log(stdout, "Service %s on host %s disappeared\n", - type, host); + write_log(stdout, "Host %s does not match distribution and/or architecture.\n", canonical); + goto finish_service; + } - remove_host(host, protocol, type); + /* add the peer to our struct */ + add_host(canonical, port, 1); - free(host); + goto finish_service; - break; +parse_failure_service: + write_log(stderr, "Parse failure for service: %s\n", strerror(-r)); - case AVAHI_BROWSER_ALL_FOR_NOW: - case AVAHI_BROWSER_CACHE_EXHAUSTED: - break; +finish_service: + free(peer); + sd_bus_message_unref(reply_service); } + + r = sd_bus_message_exit_container(reply_record); + if (r < 0) + goto parse_failure_record; + r = sd_bus_message_read(reply_record, "t", &flags); + if (r < 0) + goto parse_failure_record; + + goto finish; + +parse_failure_record: + write_log(stderr, "Parse failure for record: %s\n", strerror(-r)); + +finish: + sd_bus_message_unref(reply_record); } -/*** client_callback ***/ -static void client_callback(AvahiClient *c, - AvahiClientState state, - void * userdata) { - assert(c); +/*** add_host ***/ +static int add_host(const char * host, const uint16_t port, const uint8_t mdns) { + struct hosts * hosts_ptr = hosts; - if (state == AVAHI_CLIENT_FAILURE) { - write_log(stderr, "Server connection failure: %s\n", avahi_strerror(avahi_client_errno(c))); - avahi_simple_poll_quit(simple_poll); + while (hosts_ptr->host != NULL) { + if (strcmp(hosts_ptr->host, host) == 0) { + /* host already exists */ + if (verbose > 0) + write_log(stdout, "Updating host %s with port %d\n", + host, port); + goto update; + } + hosts_ptr = hosts_ptr->next; } + + /* host not found, adding a new one */ + if (verbose > 0) + write_log(stdout, "Adding host %s with port %d\n", + host, port); + + hosts_ptr->host = strdup(host); + hosts_ptr->mdns = mdns; + hosts_ptr->badtime = 0; + hosts_ptr->badcount = 0; + hosts_ptr->finds = 0; + + hosts_ptr->next = malloc(sizeof(struct hosts)); + hosts_ptr->next->host = NULL; + hosts_ptr->next->next = NULL; + +update: + hosts_ptr->port = port; + hosts_ptr->online = 1; + hosts_ptr->present = 1; + + return EXIT_SUCCESS; } +/*** remove_host ***/ +/* currently unused, but could become important if continuous + mDNS querying becomes available again on day... +static int remove_host(const char * host) { + struct hosts * hosts_ptr = hosts; + + while (hosts_ptr->host != NULL) { + if (strcmp(hosts_ptr->host, host) == 0) { + if (verbose > 0) + write_log(stdout, "Marking host %s offline\n", host); + hosts_ptr->online = 0; + break; + } + hosts_ptr = hosts_ptr->next; + } + + return EXIT_SUCCESS; +} */ + /*** get_http_code ***/ static void * get_http_code(void * data) { struct request * request = (struct request *)data; @@ -299,9 +502,9 @@ static void * get_http_code(void * data) { curl_easy_setopt(curl, CURLOPT_NOBODY, 1L); /* ask for filetime */ curl_easy_setopt(curl, CURLOPT_FILETIME, 1L); - /* set connection timeout to 5 seconds + /* set connection timeout to 2 seconds * if the host needs longer we do not want to use it anyway ;) */ - curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5L); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 2L); /* time out if connection is established but transfer rate is low * this should make curl finish after a maximum of 8 seconds */ curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1L); @@ -352,9 +555,101 @@ static void * get_http_code(void * data) { return NULL; } +/* append_string */ +static char * append_string(char * string, const char *format, ...) { + va_list args; + size_t string_len = 0, append_len; + + if (string != NULL) + string_len = strlen(string); + + va_start(args, format); + append_len = vsnprintf(NULL, 0, format, args) + 1; + va_end(args); + + string = realloc(string, string_len + append_len); + + va_start(args, format); + vsnprintf(string + string_len, append_len, format, args); + va_end(args); + + return string; +} +/*** status_page ***/ +static char * status_page(void) { + struct ignore_interfaces * ignore_interfaces_ptr = ignore_interfaces; + struct hosts * hosts_ptr = hosts; + char *page = NULL, *overall = CIRCLE_BLUE; + char hostname[HOST_NAME_MAX]; + struct timeval tv; + + /* initialize struct timeval */ + gettimeofday(&tv, NULL); + + if (count_redirect + count_not_found) + switch (count_redirect * 4 / (count_redirect + count_not_found)) { + case 0: + overall = CIRCLE_RED; + break; + case 1: + overall = CIRCLE_ORANGE; + break; + case 2: + overall = CIRCLE_YELLOW; + break; + default: + overall = CIRCLE_GREEN; + break; + } + + gethostname(hostname, HOST_NAME_MAX); + page = append_string(page, STATUS_HEAD, hostname, count_redirect, count_not_found, overall); + + page = append_string(page, STATUS_INT_HEAD); + if (ignore_interfaces_ptr->interface == NULL) + page = append_string(page, STATUS_INT_NONE); + while (ignore_interfaces_ptr->interface != NULL) { + if (ignore_interfaces_ptr->ifindex > 0) + /* write_log(stdout, STATUS_INT_ONE, + ignore_interfaces_ptr->interface, ignore_interfaces_ptr->ifindex); */ + page = append_string(page, STATUS_INT_ONE, + ignore_interfaces_ptr->interface, ignore_interfaces_ptr->ifindex); + else + page = append_string(page, STATUS_INT_ONE_NA, + ignore_interfaces_ptr->interface); + + ignore_interfaces_ptr = ignore_interfaces_ptr->next; + } + page = append_string(page, STATUS_INT_FOOT); + + page = append_string(page, STATUS_HOST_HEAD); + if (hosts_ptr->host == NULL) + page = append_string(page, STATUS_HOST_NONE); + while (hosts_ptr->host != NULL) { + time_t badtime = hosts_ptr->badtime + hosts_ptr->badcount * BADTIME; + uint8_t bad = hosts_ptr->badcount && badtime > tv.tv_sec ? 1 : 0; + + page = append_string(page, STATUS_HOST_ONE, + (hosts_ptr->mdns && !hosts_ptr->online) || bad ? " class=\"grey\"" : "", + hosts_ptr->host, hosts_ptr->port, + hosts_ptr->mdns ? (hosts_ptr->online ? CIRCLE_GREEN : CIRCLE_RED) : CIRCLE_BLUE, + hosts_ptr->mdns ? (hosts_ptr->online ? "online" : "offline") : "static", + hosts_ptr->finds ? CIRCLE_GREEN : CIRCLE_BLUE, hosts_ptr->finds, + bad ? CIRCLE_RED : CIRCLE_BLUE, + hosts_ptr->badcount); + + hosts_ptr = hosts_ptr->next; + } + page = append_string(page, STATUS_HOST_FOOT); + + page = append_string(page, STATUS_FOOT); + + return page; +} + /*** ahc_echo *** * called whenever a http request is received */ -static mhd_result ahc_echo(void * cls, +static enum MHD_Result ahc_echo(void * cls, struct MHD_Connection * connection, const char * uri, const char * method, @@ -365,7 +660,7 @@ static mhd_result ahc_echo(void * cls, static int dummy; struct MHD_Response * response; int ret; - struct hosts * tmphosts = hosts; + struct hosts * hosts_ptr = hosts; char * url = NULL, * page = NULL; const char * basename, * host = NULL; @@ -379,13 +674,33 @@ static mhd_result ahc_echo(void * cls, pthread_t * tid = NULL; struct request ** requests = NULL; struct request * request = NULL; - long http_code = 0; + long http_code = MHD_HTTP_NOT_FOUND; double time_total = INFINITY; char ctime[26]; /* initialize struct timeval */ gettimeofday(&tv, NULL); + /* give status page */ + if (strcmp(uri, "/") == 0) { + http_code = MHD_HTTP_OK; + page = status_page(); + goto response; + } + + /* give favicon */ + if (strcmp(uri, "/favicon.png") == 0) { + http_code = MHD_HTTP_OK; + goto response; + } + + /* give a simple ok response for monitoring */ + if (strcmp(uri, "/check") == 0) { + http_code = MHD_HTTP_OK; + page = strdup("OK"); + goto response; + } + /* we want the filename, not the path */ basename = uri; while (strstr(basename, "/") != NULL) @@ -409,15 +724,6 @@ static mhd_result ahc_echo(void * cls, /* clear context pointer */ *ptr = NULL; - /* redirect to website if no file given */ - if (*basename == 0) { - http_code = MHD_HTTP_OK; - /* duplicate string so we can free it later */ - url = strdup(WEBSITE); - host = basename = "project site"; - goto response; - } - /* process db file request (*.db and *.files) */ if ((strlen(basename) > 3 && strcmp(basename + strlen(basename) - 3, ".db") == 0) || (strlen(basename) > 6 && strcmp(basename + strlen(basename) - 6, ".files") == 0)) { @@ -434,16 +740,15 @@ static mhd_result ahc_echo(void * cls, } /* try to find a peer with most recent file */ - while (tmphosts->host != NULL) { - struct hosts * host = tmphosts; - time_t badtime = host->badtime + host->badcount * BADTIME; + while (hosts_ptr->host != NULL) { + time_t badtime = hosts_ptr->badtime + hosts_ptr->badcount * BADTIME; /* skip host if offline or had a bad request within last BADTIME seconds */ - if (host->online == 0) { + if (hosts_ptr->online == 0) { if (verbose > 0) - write_log(stdout, "Service %s on host %s is offline, skipping\n", - PACSERVE, tmphosts->host); - tmphosts = tmphosts->next; + write_log(stdout, "Host %s is offline, skipping\n", + hosts_ptr->host); + hosts_ptr = hosts_ptr->next; continue; } else if (badtime > tv.tv_sec) { if (verbose > 0) { @@ -451,10 +756,10 @@ static mhd_result ahc_echo(void * cls, ctime_r(&badtime, ctime); ctime[strlen(ctime) - 1] = '\0'; - write_log(stdout, "Service %s on host %s is marked bad until %s, skipping\n", - PACSERVE, tmphosts->host, ctime); + write_log(stdout, "Host %s is marked bad until %s, skipping\n", + hosts_ptr->host, ctime); } - tmphosts = tmphosts->next; + hosts_ptr = hosts_ptr->next; continue; } @@ -481,18 +786,18 @@ static mhd_result ahc_echo(void * cls, request = requests[req_count]; /* prepare request struct */ - request->host = tmphosts; - request->url = get_url(request->host->host, request->host->proto, request->host->address, request->host->port, dbfile, basename); + request->host = hosts_ptr; + request->url = get_url(request->host->host, request->host->port, dbfile, basename); request->http_code = 0; request->last_modified = 0; if (verbose > 0) - write_log(stdout, "Trying %s: %s\n", request->host, request->url); + write_log(stdout, "Trying %s: %s\n", request->host->host, request->url); if ((error = pthread_create(&tid[req_count], NULL, get_http_code, (void *)request)) != 0) write_log(stderr, "Could not run thread number %d, errno %d\n", req_count, error); - tmphosts = tmphosts->next; + hosts_ptr = hosts_ptr->next; } /* try to find a suitable response */ @@ -527,11 +832,12 @@ static mhd_result ahc_echo(void * cls, request->time_total < time_total))) || /* for packages try to guess the fastest peer */ (dbfile == 0 && request->time_total < time_total))) { + request->host->finds++; if (url != NULL) free(url); url = request->url; host = request->host->host; - http_code = MHD_HTTP_OK; + http_code = MHD_HTTP_TEMPORARY_REDIRECT; last_modified = request->last_modified; time_total = request->time_total; } else @@ -541,22 +847,34 @@ static mhd_result ahc_echo(void * cls, /* increase counters before reponse label, do not count redirects to project page */ - if (http_code == MHD_HTTP_OK) + if (http_code == MHD_HTTP_TEMPORARY_REDIRECT) count_redirect++; else count_not_found++; response: /* give response */ - if (http_code == MHD_HTTP_OK) { + if (http_code == MHD_HTTP_TEMPORARY_REDIRECT) { write_log(stdout, "Redirecting to %s: %s\n", host, url); page = malloc(strlen(PAGE307) + strlen(url) + strlen(basename) + 1); sprintf(page, PAGE307, url, basename); response = MHD_create_response_from_buffer(strlen(page), (void*) page, MHD_RESPMEM_MUST_FREE); ret = MHD_add_response_header(response, "Location", url); - ret = MHD_queue_response(connection, MHD_HTTP_TEMPORARY_REDIRECT, response); free(url); - } else { + } else if (http_code == MHD_HTTP_OK) { + if (page != NULL) { + write_log(stdout, "Sending status page.\n"); + response = MHD_create_response_from_buffer(strlen(page), (void*) page, MHD_RESPMEM_MUST_FREE); + ret = MHD_add_response_header(response, "Content-Type", "text/html"); + } else { + write_log(stdout, "Sending favicon.\n"); + response = MHD_create_response_from_buffer(sizeof(favicon), favicon, MHD_RESPMEM_PERSISTENT); + ret = MHD_add_response_header(response, "ETag", FAVICON_SHA1); + ret = MHD_add_response_header(response, "Last-Modified", FAVICON_DATE); + ret = MHD_add_response_header(response, "Cache-Control", "max-age=86400"); + ret = MHD_add_response_header(response, "Content-Type", "image/png"); + } + } else { /* MHD_HTTP_NOT_FOUND */ if (req_count < 0) write_log(stdout, "Currently no peers are available to check for %s.\n", basename); @@ -570,9 +888,10 @@ response: page = malloc(strlen(PAGE404) + strlen(basename) + 1); sprintf(page, PAGE404, basename); response = MHD_create_response_from_buffer(strlen(page), (void*) page, MHD_RESPMEM_MUST_FREE); - ret = MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, response); } + ret = MHD_add_response_header(response, "Server", PROGNAME " v" VERSION " " ID "/" ARCH); + ret = MHD_queue_response(connection, http_code, response); MHD_destroy_response(response); /* report counts to systemd */ @@ -588,23 +907,62 @@ response: } /*** sig_callback ***/ -void sig_callback(int signal) { +static void sig_callback(int signal) { write_log(stdout, "Received signal '%s', quitting.\n", strsignal(signal)); - avahi_simple_poll_quit(simple_poll); + quit++; } /*** sighup_callback ***/ -void sighup_callback(int signal) { - struct hosts * tmphosts = hosts; +static void sighup_callback(int signal) { + struct hosts * hosts_ptr = hosts; - write_log(stdout, "Received SIGHUP, resetting bad status for hosts.\n"); + write_log(stdout, "Received signal '%s', resetting bad counts, updating interfaces and hosts.\n", + strsignal(signal)); - while (tmphosts->host != NULL) { - tmphosts->badtime = 0; - tmphosts->badcount = 0; - tmphosts = tmphosts->next; + while (hosts_ptr->host != NULL) { + hosts_ptr->badtime = 0; + hosts_ptr->badcount = 0; + hosts_ptr = hosts_ptr->next; } + + update++; +} + +/*** sigusr_callback ***/ +static void sigusr_callback(int signal) { + struct ignore_interfaces * ignore_interfaces_ptr = ignore_interfaces; + struct hosts * hosts_ptr = hosts; + + write_log(stdout, "Received signal '%s', dumping state.\n", strsignal(signal)); + + write_log(stdout, "Ignored interfaces:\n"); + if (ignore_interfaces_ptr->interface == NULL) + write_log(stdout, " (none)\n"); + while (ignore_interfaces_ptr->interface != NULL) { + if (ignore_interfaces_ptr->ifindex > 0) + write_log(stdout, " -> %s (link %d)\n", + ignore_interfaces_ptr->interface, ignore_interfaces_ptr->ifindex); + else + write_log(stdout, " -> %s (N/A)\n", ignore_interfaces_ptr->interface); + + ignore_interfaces_ptr = ignore_interfaces_ptr->next; + } + + write_log(stdout, "Known hosts:\n"); + if (hosts_ptr->host == NULL) + write_log(stdout, " (none)\n"); + while (hosts_ptr->host != NULL) { + write_log(stdout, " -> %s (%s, %s, port: %d, finds: %d, bad: %d)\n", + hosts_ptr->host, hosts_ptr->mdns ? "mdns" : "static", + hosts_ptr->online ? "online" : "offline", hosts_ptr->port, + hosts_ptr->finds, hosts_ptr->badcount); + + hosts_ptr = hosts_ptr->next; + } + + write_log(stdout, "%d redirects, %d not found.\n", + count_redirect, count_not_found); } /*** main ***/ @@ -612,14 +970,11 @@ int main(int argc, char ** argv) { dictionary * ini; const char * inistring; char * values, * value; - int8_t use_proto = AVAHI_PROTO_UNSPEC; uint16_t port; - struct ignore_interfaces * tmp_ignore_interfaces; - AvahiClient *client = NULL; - AvahiServiceBrowser *pacserve = NULL; - int error, i, ret = 1; + struct ignore_interfaces * ignore_interfaces_ptr; + int i, ret = 1, sleepsec = 0; struct MHD_Daemon * mhd; - struct hosts * tmphosts; + struct hosts * hosts_ptr; struct sockaddr_in address; unsigned int version = 0, help = 0; @@ -642,10 +997,7 @@ int main(int argc, char ** argv) { if (verbose > 0) write_log(stdout, "%s: " PROGNAME " v" VERSION " " ID "/" ARCH -#if REPRODUCIBLE == 0 - " (compiled: " __DATE__ ", " __TIME__ ")" -#endif - "\n", argv[0]); + " (built: " __DATE__ ", " __TIME__ ")\n", argv[0]); if (help > 0) write_log(stdout, "usage: %s [-h] [-v] [-V]\n", argv[0]); @@ -670,12 +1022,15 @@ int main(int argc, char ** argv) { ignore_interfaces = malloc(sizeof(struct ignore_interfaces)); ignore_interfaces->interface = NULL; + ignore_interfaces->ifindex = 0; ignore_interfaces->next = NULL; /* Probing for static pacserve hosts takes some time. * Receiving a SIGHUP at this time could kill us. So register signal * SIGHUP here before probing. */ - signal(SIGHUP, sighup_callback); + struct sigaction act_hup = { 0 }; + act_hup.sa_handler = sighup_callback; + sigaction(SIGHUP, &act_hup, NULL); /* parse config file */ if ((ini = iniparser_load(CONFIGFILE)) == NULL) { @@ -683,7 +1038,6 @@ int main(int argc, char ** argv) { /* continue anyway, there is nothing essential in the config file */ } else { int ini_verbose; - const char * tmp; /* extra verbosity from config */ ini_verbose = iniparser_getint(ini, "general:verbose", 0); @@ -697,52 +1051,36 @@ int main(int argc, char ** argv) { /* store interfaces to ignore */ if ((inistring = iniparser_getstring(ini, "general:ignore interfaces", NULL)) != NULL) { values = strdup(inistring); - tmp_ignore_interfaces = ignore_interfaces; + ignore_interfaces_ptr = ignore_interfaces; value = strtok(values, DELIMITER); while (value != NULL) { if (verbose > 0) write_log(stdout, "Ignoring interface: %s\n", value); - tmp_ignore_interfaces->interface = strdup(value); - tmp_ignore_interfaces->next = malloc(sizeof(struct ignore_interfaces)); - tmp_ignore_interfaces = tmp_ignore_interfaces->next; + ignore_interfaces_ptr->interface = strdup(value); + ignore_interfaces_ptr->next = malloc(sizeof(struct ignore_interfaces)); + ignore_interfaces_ptr = ignore_interfaces_ptr->next; value = strtok(NULL, DELIMITER); } - tmp_ignore_interfaces->interface = NULL; - tmp_ignore_interfaces->next = NULL; + ignore_interfaces_ptr->interface = NULL; + ignore_interfaces_ptr->next = NULL; free(values); } - /* configure protocols to use */ - if ((tmp = iniparser_getstring(ini, "general:protocol", NULL)) != NULL) { - switch(tmp[strlen(tmp) - 1]) { - case '4': - if (verbose > 0) - write_log(stdout, "Using IPv4 only\n"); - use_proto = AVAHI_PROTO_INET; - break; - case '6': - if (verbose > 0) - write_log(stdout, "Using IPv6 only\n"); - use_proto = AVAHI_PROTO_INET6; - break; - } - } - /* add static pacserve hosts */ if ((inistring = iniparser_getstring(ini, "general:pacserve hosts", NULL)) != NULL) { values = strdup(inistring); value = strtok(values, DELIMITER); while (value != NULL) { if (verbose > 0) - write_log(stdout, "Adding static pacserve host: %s\n", value); + write_log(stdout, "Adding static host: %s\n", value); if (strchr(value, ':') != NULL) { port = atoi(strchr(value, ':') + 1); *strchr(value, ':') = 0; } else port = PORT_PACSERVE; - add_host(value, AVAHI_PROTO_UNSPEC, NULL, port, PACSERVE); + add_host(value, port, 0); value = strtok(NULL, DELIMITER); } free(values); @@ -752,29 +1090,10 @@ int main(int argc, char ** argv) { iniparser_freedict(ini); } - /* allocate main loop object */ - if ((simple_poll = avahi_simple_poll_new()) == NULL) { - write_log(stderr, "Failed to create simple poll object.\n"); - goto fail; - } - - /* allocate a new client */ - if ((client = avahi_client_new(avahi_simple_poll_get(simple_poll), 0, client_callback, NULL, &error)) == NULL) { - write_log(stderr, "Failed to create client: %s\n", avahi_strerror(error)); - goto fail; - } - - /* create the service browser for PACSERVE */ - if ((pacserve = avahi_service_browser_new(client, AVAHI_IF_UNSPEC, - use_proto, PACSERVE, NULL, 0, browse_callback, client)) == NULL) { - write_log(stderr, "Failed to create service browser: %s\n", avahi_strerror(avahi_client_errno(client))); - goto fail; - } - /* prepare struct to make microhttpd listen on localhost only */ address.sin_family = AF_INET; address.sin_port = htons(PORT_PACREDIR); - address.sin_addr.s_addr = htonl(0x7f000001); + address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); /* start http server */ if ((mhd = MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION | MHD_USE_TCP_FASTOPEN, PORT_PACREDIR, @@ -783,19 +1102,40 @@ int main(int argc, char ** argv) { goto fail; } + if (verbose > 0) + write_log(stdout, "Listening on port %d\n", PORT_PACREDIR); + /* initialize curl */ curl_global_init(CURL_GLOBAL_ALL); - /* register SIG{TERM,KILL,INT} signal callbacks */ - signal(SIGTERM, sig_callback); - signal(SIGKILL, sig_callback); - signal(SIGINT, sig_callback); + /* register SIG{INT,KILL,TERM} signal callbacks */ + struct sigaction act = { 0 }; + act.sa_handler = sig_callback; + sigaction(SIGINT, &act, NULL); + sigaction(SIGKILL, &act, NULL); + sigaction(SIGTERM, &act, NULL); + + /* register SIGUSR[12] signal callbacks */ + struct sigaction act_usr = { 0 }; + act_usr.sa_handler = sigusr_callback; + sigaction(SIGUSR1, &act_usr, NULL); + sigaction(SIGUSR2, &act_usr, NULL); /* report ready to systemd */ sd_notify(0, "READY=1\nSTATUS=Waiting for requests to redirect..."); - /* run the main loop */ - avahi_simple_poll_loop(simple_poll); + /* main loop */ + while (quit == 0) { + sleepsec = sleep(sleepsec); + + if (sleepsec > 0 && update == 0) + continue; + + update_interfaces(); + update_hosts(); + update = 0; + sleepsec = 60; + } /* report stopping to systemd */ sd_notify(0, "STOPPING=1\nSTATUS=Stopping..."); @@ -813,26 +1153,19 @@ fail: /* Cleanup things */ while (hosts->host != NULL) { free(hosts->host); - tmphosts = hosts->next; + hosts_ptr = hosts->next; free(hosts); - hosts = tmphosts; + hosts = hosts_ptr; } + free(hosts); while (ignore_interfaces->interface != NULL) { free(ignore_interfaces->interface); - tmp_ignore_interfaces = ignore_interfaces->next; + ignore_interfaces_ptr = ignore_interfaces->next; free(ignore_interfaces); - ignore_interfaces = tmp_ignore_interfaces; + ignore_interfaces = ignore_interfaces_ptr; } - - if (pacserve) - avahi_service_browser_free(pacserve); - - if (client) - avahi_client_free(client); - - if (simple_poll) - avahi_simple_poll_free(simple_poll); + free(ignore_interfaces); sd_notify(0, "STATUS=Stopped. Bye!"); @@ -1,5 +1,5 @@ /* - * (C) 2013-2024 by Christian Hesse <mail@eworm.de> + * (C) 2013-2025 by Christian Hesse <mail@eworm.de> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -35,13 +35,9 @@ #include <time.h> /* systemd headers */ +#include <systemd/sd-bus.h> #include <systemd/sd-daemon.h> -/* Avahi headers */ -#include <avahi-client/lookup.h> -#include <avahi-common/error.h> -#include <avahi-common/simple-watch.h> - /* various headers needing linker options */ #include <curl/curl.h> #include <iniparser/iniparser.h> @@ -51,32 +47,44 @@ /* compile time configuration */ #include "config.h" #include "version.h" +#include "html.h" +#include "favicon.h" -#define PROGNAME "pacredir" +#define DNS_CLASS_IN 1U +#define DNS_TYPE_PTR 12U + +#define DNS_SRV_TXT_MATCH_ARCH (1 << 0) +#define DNS_SRV_TXT_MATCH_ID (1 << 1) +#define DNS_SRV_TXT_MATCH_ALL ((1 << 2) - 1) -/* libmicrohttpd compat */ -#if MHD_VERSION >= 0x00097002 -# define mhd_result enum MHD_Result -#else -# define mhd_result int -#endif +#define SD_RESOLVED_NO_SYNTHESIZE (UINT64_C(1) << 11) +#define SD_RESOLVED_NO_ZONE (UINT64_C(1) << 13) + +#define PROGNAME "pacredir" /* hosts */ struct hosts { /* host name */ char * host; - /* protocol (AVAHI_PROTO_INET, AVAHI_PROTO_INET6 or AVAHI_PROTO_UNSPEC) */ - AvahiProtocol proto; - /* resolved address */ - char address[AVAHI_ADDRESS_STR_MAX]; + + /* /\ Every now and then I think about adding ip addresses here. We have + /\7\ these from mDNS query anyway, but: pacman has to succeed with the + /_()_\ url we send it, so do roughly the same, including resolving. */ + /* network port */ uint16_t port; + /* true for hosts from mDNS (vs. static) */ + uint8_t mdns; /* true if host/service is online */ uint8_t online; + /* intermediate state while querying mDNS */ + uint8_t present; /* unix timestamp of last bad request */ __time_t badtime; /* count the number of bad requests */ unsigned int badcount; + /* count finds */ + unsigned int finds; /* pointer to next struct element */ struct hosts * next; }; @@ -85,6 +93,8 @@ struct hosts { struct ignore_interfaces { /* interface name */ char * interface; + /* interface index */ + unsigned int ifindex; /* pointer to next struct element */ struct ignore_interfaces * next; }; @@ -104,50 +114,34 @@ struct request { }; /* write_log */ -int write_log(FILE *stream, const char *format, ...); -/* get_fqdn */ -char * get_fqdn(const char * hostname, const char * domainname); +static int write_log(FILE *stream, const char *format, ...); /* get_url */ -char * get_url(const char * hostname, AvahiProtocol proto, const char * address, const uint16_t port, const uint8_t dbfile, const char * uri); +static char * get_url(const char * hostname, const uint16_t port, const uint8_t dbfile, const char * uri); +/* update_interfaces */ +static void update_interfaces(void); + +/* get_name */ +static size_t get_name(const uint8_t* rr_ptr, char* name); +/* process_reply_record */ +static char* process_reply_record(const void *rr, size_t sz); +/* update_hosts */ +static void update_hosts(void); +/* update_hosts_on_interface */ +static void update_hosts_on_interface(sd_bus *bus, const unsigned int if_index); /* add_host */ -int add_host(const char * host, AvahiProtocol proto, const char * address, const uint16_t port, const char * type); +static int add_host(const char * host, const uint16_t port, const uint8_t mdns); /* remove_host */ -int remove_host(const char * host, AvahiProtocol proto, const char * type); - -/* resolve_callback */ -static void resolve_callback(AvahiServiceResolver *r, - AvahiIfIndex interface, - AvahiProtocol protocol, - AvahiResolverEvent event, - const char *name, - const char *type, - const char *domain, - const char *host, - const AvahiAddress *address, - uint16_t port, - AvahiStringList *txt, - AvahiLookupResultFlags flags, - void* userdata); -/* browse_callback */ -static void browse_callback(AvahiServiceBrowser *b, - AvahiIfIndex interface, - AvahiProtocol protocol, - AvahiBrowserEvent event, - const char *name, - const char *type, - const char *domain, - AvahiLookupResultFlags flags, - void* userdata); -/* client_callback */ -static void client_callback(AvahiClient *c, - AvahiClientState state, - void * userdata); +/* static int remove_host(const char * host); */ /* get_http_code */ static void * get_http_code(void * data); +/* append_string */ +static char * append_string(char * string, const char *format, ...); +/* status_page */ +static char * status_page(void); /* ahc_echo */ -static mhd_result ahc_echo(void * cls, +static enum MHD_Result ahc_echo(void * cls, struct MHD_Connection * connection, const char * uri, const char * method, @@ -157,9 +151,11 @@ static mhd_result ahc_echo(void * cls, void ** ptr); /* sig_callback */ -void sig_callback(int signal); +static void sig_callback(int signal); /* sighup_callback */ -void sighup_callback(int signal); +static void sighup_callback(int signal); +/* sigusr_callback */ +static void sigusr_callback(int signal); #endif /* _PACREDIR_H */ diff --git a/systemd/pacredir.service b/systemd/pacredir.service index ed5e45a..ad267a2 100644 --- a/systemd/pacredir.service +++ b/systemd/pacredir.service @@ -1,4 +1,4 @@ -# (C) 2013-2024 by Christian Hesse <mail@eworm.de> +# (C) 2013-2025 by Christian Hesse <mail@eworm.de> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -6,10 +6,10 @@ # (at your option) any later version. [Unit] -Description=Redirect pacman requests via avahi service -Requires=avahi-daemon.service -After=avahi-daemon.service network.target network-online.target -Documentation=https://github.com/eworm-de/pacredir#pacredir +Description=Redirect pacman requests via mDNS Service Discovery +Documentation=https://pacredir.eworm.de/ +Requires=systemd-resolved.service +After=network.target network-online.target systemd-resolved.service [Service] Type=notify diff --git a/systemd/pacserve.service b/systemd/pacserve.service deleted file mode 100644 index d5e064d..0000000 --- a/systemd/pacserve.service +++ /dev/null @@ -1,22 +0,0 @@ -# (C) 2013-2024 by Christian Hesse <mail@eworm.de> -# -# This program 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. - -[Unit] -Description=Serve pacman database files and package archives -After=systemd-tmpfiles-setup.service network.target - -[Service] -ExecStart=/usr/bin/pacserve /run/pacserve/ --ipv6 --port 7078 --no-listing --index empty -BindReadOnlyPaths=/var/cache/pacman/pkg:/run/pacserve/pkg /var/lib/pacman/sync:/run/pacserve/db -DynamicUser=on -ProtectSystem=full -ProtectHome=on -PrivateDevices=on -NoNewPrivileges=on - -[Install] -WantedBy=multi-user.target diff --git a/systemd/pacserve.service.in b/systemd/pacserve.service.in new file mode 100644 index 0000000..7d25640 --- /dev/null +++ b/systemd/pacserve.service.in @@ -0,0 +1,28 @@ +# (C) 2013-2025 by Christian Hesse <mail@eworm.de> +# +# This program 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. + +[Unit] +Description=Serve pacman database files and package archives +Documentation=https://pacredir.eworm.de/ +Requires=systemd-resolved.service +After=systemd-tmpfiles-setup.service network.target systemd-resolved.service + +[Service] +EnvironmentFile=/etc/pacserve.conf +ExecStart=/usr/bin/pacserve /run/pacserve/ --ipv6 --port ${PORT} --no-listing --index empty +# magic numbers at the end represent TXT data: id=%ID% arch=%ARCH% +ExecStartPost=+/usr/bin/busctl --quiet call org.freedesktop.resolve1 /org/freedesktop/resolve1 org.freedesktop.resolve1.Manager RegisterService sssqqqaa{say} pacserve 'pacserve on %l' _pacserve._tcp ${PORT} 0 0 1 2 id %ID_BYTES% arch %ARCH_BYTES% +ExecStopPost=+/usr/bin/busctl --quiet call org.freedesktop.resolve1 /org/freedesktop/resolve1 org.freedesktop.resolve1.Manager UnregisterService o /org/freedesktop/resolve1/dnssd/pacserve +BindReadOnlyPaths=/var/cache/pacman/pkg:/run/pacserve/pkg /var/lib/pacman/sync:/run/pacserve/db +DynamicUser=on +ProtectSystem=full +ProtectHome=on +PrivateDevices=on +NoNewPrivileges=on + +[Install] +WantedBy=multi-user.target diff --git a/systemd/sysusers.conf b/systemd/sysusers.conf index cd2e81c..ced5210 100644 --- a/systemd/sysusers.conf +++ b/systemd/sysusers.conf @@ -1 +1 @@ -u pacredir - "redirect pacman requests" +u! pacredir - "redirect pacman requests" |