Skip to content

Commit

Permalink
Merge pull request #8 from grishy/subdomain
Browse files Browse the repository at this point in the history
Add subdomain support
  • Loading branch information
grishy authored Aug 8, 2023
2 parents a2a884f + 9825387 commit 14e7f7f
Show file tree
Hide file tree
Showing 13 changed files with 475 additions and 143 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/commit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,20 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: "1.20"
go-version: "1.20.7"
cache: false

- name: golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: v1.53
version: v1.53.3
build_test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: "1.20"
go-version: "1.20.7"
cache: false

- name: Verify dependencies
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:

- uses: actions/setup-go@v4
with:
go-version: "1.20.6"
go-version: "1.20.7"

- name: Set up QEMU
uses: docker/setup-qemu-action@v2
Expand Down
6 changes: 5 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# syntax=docker/dockerfile:1

# STAGE 1: building the executable
FROM docker.io/golang:1.20.6-alpine3.18 as builder
FROM docker.io/golang:1.20.7-alpine3.18 as builder
WORKDIR /build

COPY go.mod go.sum ./
Expand All @@ -15,4 +15,8 @@ RUN go build -ldflags="-w -s" -o /go-avahi-cname
# STAGE 2: build the container to run
FROM scratch
COPY --from=builder /go-avahi-cname /go-avahi-cname

EXPOSE 5353/udp

ENTRYPOINT ["/go-avahi-cname"]
CMD [ "subdomain" ]
141 changes: 97 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,49 +1,100 @@
<img src="./docs/logo_x3.png" width="350">
<p align="center">
<img src="./docs/logo_x3.png" width="350">
</p>

![GitHub tag (with filter)](https://img.shields.io/github/v/tag/grishy/go-avahi-cname)
[![Go Report Card](https://goreportcard.com/badge/github.com/grishy/go-avahi-cname)](https://goreportcard.com/report/github.com/grishy/go-avahi-cname)
![Build Status](https://github.com/grishy/go-avahi-cname/actions/workflows/release.yml/badge.svg)
<p align="center">
<img src="https://img.shields.io/github/v/tag/grishy/go-avahi-cname" alt="GitHub tag (with filter)">
<img src="https://goreportcard.com/badge/github.com/grishy/go-avahi-cname" alt="Go Report Card">
<img src="https://github.com/grishy/go-avahi-cname/actions/workflows/release.yml/badge.svg" alt="Build Status">
</p>

## What is go-avahi-cname?
## TL;DR

Forward all subdomains current to machine( `*.hostname.local` -> `hostname.local`)

1. _Binary_ `./go-avahi-cname subdomain`
2. _Docker_ `docker run -d --network host -v "/var/run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket" ghcr.io/grishy/go-avahi-cname:v2.0.0`

# What is go-avahi-cname?

It is a simple and lightweight project that allows you to publish CNAME records pointing to the local host over multicast DNS using the **Avahi** daemon, which is widely available in most Linux distributions. This means that you can access your local host using different names from any device on the same network, as long as they support Apple’s Bonjour protocol, which is compatible with Avahi.

### Goals
## Goals

Here are some of the benefits of using go-avahi-cname:

- **✅ No dependencies**
You only need the Avahi daemon running on your host, no other libraries or packages are required.
- **✅ Small footprint**
The binary size is less than 3MB, and it consumes minimal resources while running.
- **✅ Support x86_64 and ARM**
- **✅ Release binaries and containers**
- **✅ No dependencies** - Requires only the Avahi daemon.
- **✅ Small footprint**
- **✅ Support x86_64 and ARM**
- **✅ Install as binaries or containers**

## Modes of Operation

- **Subdomain reply** - _I think you want this._ Listen to the traffic and if someone asks `*.hostname.local` (example: `name1.hostname.local`), we "redirect" to `hostname.local`.
- **Interval publishing** - Periodically broadcasts CNAME records for various `name1.hostname.local`, `git.any.local`...

---

# How does it work?

The tool communicates with the Avahi daemon via DBus to publish CNAME records.

## Subdomain CNAME reply

![Architecture](./docs/arch_subdomain.excalidraw.svg)

```plain
> ./go-avahi-cname subdomain -h
NAME:
go-avahi-cname subdomain - Listen for all queries and publish CNAMEs for subdomains
USAGE:
go-avahi-cname subdomain [command options] [arguments...]
OPTIONS:
--ttl value TTL of CNAME record in seconds (default: 600) [$TTL]
--fqdn value FQDN which will be used for CNAME. If empty, will be used current FQDN (default: hostname.local.) [$FQDN]
--help, -h show help
```

### How does it work?
In this variant, we listen to the traffic with avahi-daemon for all questions with names and if they match ours, we send a command to avahi to answer it (send CNAME). The standard can be run without parameters, then we will resolve all requests that contain our hostname. For example, `git.lab.local` will be redirected to `lab.local`

The following diagram shows the basic architecture of go-avahi-cname:
## Interval publishing of CNAME records

![Architecture](./docs/arch.excalidraw.svg)
![Architecture](./docs/arch_cname.excalidraw.svg)

As you can see, _go-avahi-cname_ communicates with the Avahi daemon via DBus, and publishes the CNAME records that you specify as arguments. The Avahi daemon then broadcasts these records over multicast DNS, so that other devices on the same network can resolve them.
As you can see, _go-avahi-cname_ communicates with the Avahi daemon via DBus, and publishes the CNAME records that you specify as arguments.

## How to use and install?
```plain
> ./go-avahi-cname cname -h
NAME:
go-avahi-cname cname - Announce CNAME records for host via avahi-daemon
USAGE:
go-avahi-cname cname [command options] [arguments...]
OPTIONS:
--ttl value TTL of CNAME record in seconds. How long they will be valid. (default: 600) [$TTL]
--interval value Interval of publishing CNAME records in seconds. How often to send records to other machines. (default: 300) [$INTERVAL]
--fqdn value Where to redirect. If empty, the Avahi FQDN (current machine) will be used (default: hostname.local.) [$FQDN]
--help, -h show help
```

You can specify any number of CNAMEs as arguments when running go-avahi-cname, with no length limit.
You can use either just the name (`name1`), which will create a record as a subdomain for the current machine, or you can write the full FQDN (`name1.hostname.local.` domain with a dot on the end) format.

For example, if your machine’s hostname is lab, you can run:

```plain
> ./go-avahi-cname git photo.local. example.lab.local.
2023/07/27 08:37:14 Creating publisher
2023/07/27 08:37:14 Formating CNAMEs:
2023/07/27 08:37:14 > 'git.lab.local.' (added current FQDN)
2023/07/27 08:37:14 > 'photo.local.'
2023/07/27 08:37:14 > 'example.lab.local.'
2023/07/27 08:37:14 Publishing every 5m0s and CNAME TTL=600s.
^C
2023/07/27 08:37:16 Closing publisher...
> ./go-avahi-cname cname git photo.local. example.lab.local.
2023/08/08 14:51:21 Creating publisher
2023/08/08 14:51:21 Getting FQDN from Avahi
2023/08/08 14:51:21 FQDN: lab.local.
2023/08/08 14:51:21 Formatting CNAMEs:
2023/08/08 14:51:21 > 'git.lab.local.' (added FQDN)
2023/08/08 14:51:21 > 'photo.local.'
2023/08/08 14:51:21 > 'example.lab.local.'
2023/08/08 14:51:21 Publishing every 300s and CNAME TTL 600s
```

This will create three CNAME records pointing to your local host:
Expand All @@ -54,31 +105,29 @@ This will create three CNAME records pointing to your local host:

You can then access your local host using any of these names from other devices on the same network.

### Installation options
## Installation options

There are two ways to install and run go-avahi-cname:

#### Binary
### Binary

Binary files can be taken as artifacts for [the Release](https://github.com/grishy/go-avahi-cname/releases). In this case, it would be better to create a systemd service.

#### Container
### Container

The images for each version are in [the Packages section](https://github.com/grishy/go-avahi-cname/pkgs/container/go-avahi-cname).
You need to provide the `/var/run/dbus/system_bus_socket` file to the container to be able to communicate with the host's Avahi daemon.

One-liner to run the container `v0.3.1`:
Docker Composer example:

```bash
> docker run --restart=unless-stopped -d -v /var/run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket ghcr.io/grishy/go-avahi-cname:v0.3.1 name1 name2.lab.local.
5a19790e06cca93016af6651d7af4046c24095a6909ace2fe26c3451fb98ceee

> docker logs 5a19790e06cca93016af6651d7af4046c24095a6909ace2fe26c3451fb98ceee
2023/07/27 08:49:02 Creating publisher
2023/07/27 08:49:02 Formating CNAMEs:
2023/07/27 08:49:02 > 'name1.lab.local.' (added current FQDN)
2023/07/27 08:49:02 > 'name2.lab.local.'
2023/07/27 08:49:02 Publishing every 5m0s ans CNAME TTL=600s.
```yaml
version: "3.3"
services:
go-avahi-cname:
network_mode: host
volumes:
- "/var/run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket"
image: "ghcr.io/grishy/go-avahi-cname:v2.0.0"
```
Ansible task to run the container:
Expand All @@ -87,17 +136,21 @@ Ansible task to run the container:
- name: go-avahi-cname | Start container
community.docker.docker_container:
name: "go-avahi-cname"
image: "ghcr.io/grishy/go-avahi-cname:v0.3.1"
image: "ghcr.io/grishy/go-avahi-cname:v2.0.0"
restart_policy: unless-stopped
volumes:
- "/var/run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket" # access to avahi-daemon
command: "name1 name2 git"
```
## Source of inspiration
- https://web.archive.org/web/20151016190620/http://www.avahi.org/wiki/Examples/PythonPublishAlias
- https://pypi.org/project/mdns-publisher/
- [PythonPublishAlias](https://web.archive.org/web/20151016190620/http://www.avahi.org/wiki/Examples/PythonPublishAlias)
- [mdns-publisher](https://pypi.org/project/mdns-publisher/)
- [nfam/mdns-subdomain](https://github.com/nfam/mdns-subdomain)
## Logo
Generated by mix of Go Gopher and Avahi logo.
## License
Expand Down
25 changes: 14 additions & 11 deletions publisher/publisher.go → avahi/publisher.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package publisher
package avahi

import (
"fmt"
Expand All @@ -22,20 +22,21 @@ type Publisher struct {
rdataField []byte
}

// NewPublisher creates a new service for Publisher.
func NewPublisher() (*Publisher, error) {
conn, err := dbus.SystemBus()
if err != nil {
return nil, fmt.Errorf("can't connect to system bus: %v", err)
return nil, fmt.Errorf("failed to connect to system bus: %v", err)
}

server, err := avahi.ServerNew(conn)
if err != nil {
return nil, fmt.Errorf("can't create Avahi server: %v", err)
return nil, fmt.Errorf("failed to create Avahi server: %v", err)
}

avahiFqdn, err := server.GetHostNameFqdn()
if err != nil {
return nil, fmt.Errorf("can't get FQDN from Avahi: %v", err)
return nil, fmt.Errorf("failed to get FQDN from Avahi: %v", err)
}

fqdn := dns.Fqdn(avahiFqdn)
Expand All @@ -45,7 +46,7 @@ func NewPublisher() (*Publisher, error) {
rdataField := make([]byte, len(fqdn)+1)
_, err = dns.PackDomainName(fqdn, rdataField, 0, nil, false)
if err != nil {
return nil, fmt.Errorf("can't pack FQDN into RDATA: %v", err)
return nil, fmt.Errorf("failed to pack FQDN into RDATA: %v", err)
}

return &Publisher{
Expand All @@ -56,14 +57,16 @@ func NewPublisher() (*Publisher, error) {
}, nil
}

// Fqdn returns the fully qualified domain name from Avahi.
func (p *Publisher) Fqdn() string {
return p.fqdn
}

// PublishCNAMES send via Avahi-daemon CNAME records with the provided TTL.
func (p *Publisher) PublishCNAMES(cnames []string, ttl uint32) error {
group, err := p.avahiServer.EntryGroupNew()
if err != nil {
return fmt.Errorf("can't create entry group: %v", err)
return fmt.Errorf("failed to create entry group: %v", err)
}

for _, cname := range cnames {
Expand All @@ -78,18 +81,18 @@ func (p *Publisher) PublishCNAMES(cnames []string, ttl uint32) error {
p.rdataField,
)
if err != nil {
return fmt.Errorf("can't add record to entry group: %v", err)
return fmt.Errorf("failed to add record to entry group: %v", err)
}
}

if err := group.Commit(); err != nil {
return fmt.Errorf("can't commit entry group: %v", err)
return fmt.Errorf("failed to commit entry group: %v", err)
}

return nil
}

func (p *Publisher) Close() error {
p.avahiServer.Close()
return p.dbusConn.Close()
// Close associated resources.
func (p *Publisher) Close() {
p.avahiServer.Close() // It also close the DBus connection
}
Loading

0 comments on commit 14e7f7f

Please sign in to comment.