如何使用mDNS将所有子域路由到单个主机?

32

我有一个开发Web服务器,以"myhost.local"作为主机名,并使用Bonjour/mDNS进行发现。该服务器正在运行avahi-daemon。

该Web服务器还希望处理自身的任何子域。例如 "cat.myhost.local"、"dog.myhost.local" 和 "guppy.myhost.local"。

鉴于myhost.local在DHCP中使用动态IP地址,是否仍有办法将所有子域的请求路由到myhost.local?

我开始认为这目前不可能...

http://marc.info/?l=freedesktop-avahi&m=119561596630960&w=2

您可以使用/etc/avahi/hosts文件来完成此操作。或者,您可以使用avahi-publish-host-name。

不,他不能。因为他想定义一个别名,而不是一个新的主机名。也就是说,他只想注册A记录,没有反向PTR记录。但是,如果你把某些东西放入/etc/avahi/hosts中,那么它会同时注册两者,并且如果PTR RR不唯一,则检测到冲突,这将是别名的情况。


你使用哪个HTTP服务器?Apache?IIS?LigHTTPD? - Jordan S. Jones
Apache,但是让Apache响应子域名的请求并不是问题,问题在于如何让客户端寻找到该主机。 - John Mee
6个回答

13

我已经在分配给这个任务的时间很短的情况下尽力解决了这个问题。

但不幸的是,我认为avahi/msdns/bonjour的windows实现不支持别名(如果我错了,请举例说明如何支持)。

我从avahi网站提供的示例Python脚本开始:

创建:/usr/bin/avahi-announce-alias

让它可执行并填充它的内容。

#! /usr/bin/env python
# avahi-alias.py

import avahi, dbus
from encodings.idna import ToASCII

# Got these from /usr/include/avahi-common/defs.h
CLASS_IN = 0x01
TYPE_CNAME = 0x05

TTL = 60

def publish_cname(cname):
    bus = dbus.SystemBus()
    server = dbus.Interface(bus.get_object(avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER),
            avahi.DBUS_INTERFACE_SERVER)
    group = dbus.Interface(bus.get_object(avahi.DBUS_NAME, server.EntryGroupNew()),
            avahi.DBUS_INTERFACE_ENTRY_GROUP)

    rdata = createRR(server.GetHostNameFqdn())
    cname = encode_dns(cname)

    group.AddRecord(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, dbus.UInt32(0),
        cname, CLASS_IN, TYPE_CNAME, TTL, rdata)
    group.Commit()


def encode_dns(name):
    out = []
    for part in name.split('.'):
        if len(part) == 0: continue
        out.append(ToASCII(part))
    return '.'.join(out)

def createRR(name):
    out = []
    for part in name.split('.'):
        if len(part) == 0: continue
        out.append(chr(len(part)))
        out.append(ToASCII(part))
    out.append('\0')
    return ''.join(out)

if __name__ == '__main__':
    import time, sys, locale
    for each in sys.argv[1:]:
        name = unicode(each, locale.getpreferredencoding())
        publish_cname(name)
    try:
        # Just loop forever
        while 1: time.sleep(60)
    except KeyboardInterrupt:
        print "Exiting"

这个脚本处理每个别名的公告,并会一直运行,直到你终止它。 (因此我们需要创建另一个脚本,如下所示)

创建文本文件/etc/avahi/aliases

我们用它来存储每行一个的此机器的别名。

创建目录/etc/avahi/aliases.d/

我实际上还没有在任何我展示的脚本中使用过它,但对于那些有企图心的人来说,你可以看到需要做什么。

这个想法是你可以将别名分组到单独的文本文件中(当你处理apache中的虚拟主机时,这将更加有意义),这是许多*nix上的守护程序应用程序已经提供的功能(apache和apt只是其中两个示例)。

创建/usr/bin/avahi-announce-aliases

使其可执行并填充它。

#!/usr/bin/env python

import os, sys
from subprocess import Popen


def ensure_file (path):
    """
        Looks for  file at provided path, creates it if it does not exist.
        Returns the file.
    """
    rfile = None    
    if not os.path.exists(path) and os.path.isfile(path) :
        rfile = open(path,"w+");
        print("ensuring file : %s " % path)

    print("file ensured : %s " % path)
    return rfile


command = '/usr/bin/avahi-announce-alias'
alias_pid_path = "/tmp/avahi-aliases.pid"
alias_file_path = "/etc/avahi/aliases"

alias_file = open(alias_file_path)
if not os.path.exists(alias_pid_path) :
    open(alias_pid_path,"w").close()

alias_pid = open(alias_pid_path,"r")


for line in alias_pid :
    txt = line.strip('\n')
    if len(txt) > 0 :
        print("kill %s" % txt )
        os.system("kill %s" % txt)          
alias_pid.close()
alias_pid = open(alias_pid_path,"w+")

for line in alias_file :
    txt = line.strip('\n')
    if len(txt) > 0 :
        print("publishing : << %s >>" % txt)
        process = Popen([command, txt])
        alias_pid.write("%s\n" % str(process.pid))    
alias_pid.close()

print("done")

这并不是Python编程的巅峰,所以请随意进行改进。

用法

如果我们的主机名是“server”,而avahi主机名是“server.local”,那么我们可以在/etc/avahi/aliases文本文件中添加额外主机名:

deluge.server.local
username.server.local
accounts.server.local
something-else.server.local
another.hostname.home

但是实际上,我非常确定只要确保它在网络上不存在,你就可以使用任何主机名,这就是为什么我只创建了正常 avahi 主机名的“子域名”。

然后我们运行:

sudo avahi-publish-aliases

我设置这个的主要原因是为了方便在我的笔记本电脑上模拟 Django 和 Drupal 网站开发。

注意事项

我的唯一遗憾是,Bonjour/Avahi 的 Windows 实现不支持此实现公布的别名,它只会看到通常公布的主 avahi 主机名(即上面例子中的 server.local)。


1
这是特定于Avahi的。这对OS X的Bonjour有什么影响吗? - Jonathan Dumaine
@JonathanDumaine 就像 Windows 一样。也就是说:它不会。关于 PID 的更新说明,我已经使用 upstart 作业和 python-daemon 实现了这个功能。 - airtonix
我明白了。我相信这对于使用Avahi的机器非常有用,但OP问的是关于Bonjour/mDNS的,所以我最初认为这个答案适用于OS X,但实际上并不是。 - Jonathan Dumaine
@JonathanDumaine 实际上,OP是在问如何使Linux主机提供多个别名。只要其他网络上的主机能够读取mDNS数据包,无论它们是什么,都没有关系,因为Windows和macOS/X都可以使用Bonjour进行操作。然而,通过mDNS宣布多个CNAME在Windows和macOSX中是不可能的(据我所知),但在这种情况下,我会在虚拟机中运行Ubuntu,并使用主机模式网络。 - airtonix
使用 group.AddRecord(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, dbus.UInt32(0), cname, CLASS_IN, TYPE_A, TTL, ipaddr.IPv4Address('127.0.0.1').packed) 添加地址(A记录)。在Windows Bonjour中可以正常工作。不幸的是,Bonjour会将AAAA回复丢弃,因此只能添加IPv4(A记录)。您可以通过在VM上安装您喜欢的Linux,停止“resolved”守护程序(LLDP响应器)并保持avahi运行来验证它。如果您仅为VM分配IPv6地址,则无法从Windows访问它。 - Vitaly Greck
显示剩余5条评论

5
我当时认为这是不可能的,所以放弃了,之后也没有再去解决这个问题。
我的现有解决方案是在本地机器上费力地将每个感兴趣的子域添加到hosts文件中。

3
如果您只希望在本地主机上使用它,您可以在本地主机上运行一个普通的DNS服务器,例如dnsmasq,并使用以下设置:address=/loc/127.0.0.1 这将回复127.0.0.1给 x.loc 和 x.y.loc等。如果您设置dnsmasq使用一些全球可用的名称服务器(例如Google的),则可以将您的/etc/resolv.conf设置为localhost(dnsmasq)并使用chattr + i /etc/resolv.conf来防止dhcp或其他方式修改/etc/resolv.conf。 - JasonWoof

5

注意:似乎有一个更新版本在 https://github.com/till/avahi-aliases 上,并且我已经打开了一个添加systemd单元文件的PR。 - admalledd

4

您可以使用Avahi API注册其他记录。 Avahi维基上的此示例演示了如何在Python中发布其他CNAME(别名)记录。


1
我也需要这个,经过长时间的搜索,我意识到mDNS是一个非常受支持的协议,因此并不奇怪,有一个npm包可以使用:multicast-dns。有了它,实现所需的行为非常简单。
const os = require('os');
const mdns = require('multicast-dns')();

const IP = process.argv[2];

if (!IP) {
  console.error('You need to provide an IP as argument');
  process.exit(1);
}

const hostname = os.hostname();

mdns.on('query', function(query) {
  const name = query.questions[0].name;
  console.log('got a query, name:', name);

  if (name.endsWith(`${hostname}.local`)) {
    console.log('responding', name);
    mdns.respond({
      answers: [{
        name,
        type: 'A',
        ttl: 300,
        data: IP
      }]
    });
  }
});

这个脚本只是监听mDNS查询,检查它们是否以我们的本地主机名结尾,如果是的话,就用查询中的相同名称进行响应。
将其保存为mdns.js并运行。
npm i multicast-dns
node mdns.js SOME-IP

之后,您可以使用avahi-resolve -n foo-bar-the-great.myhost.local(如果myhost是您的主机名),它应该按预期解析为您的IP地址。当然,如果您的机器在解析主机时正确配置了使用mDNS,那么ping foo-bar-the-great.myhost.local也将起作用,任何您编写的子域名也是如此。

这太棒了,完全满足了我的需求。谢谢你。 - undefined
实际上,我需要澄清一下,根据RFC规范,答案名称字段必须以'.'结尾。除此之外,再次感谢你的帮助:D - undefined

0
一个更好的解决方案是dnsmasq
这可以通过参数/配置非常简单地实现...
返回指定域中所有主机的ip地址。
-A, --address=// address=/yourdomain.com/10.0.1.100

3
我认为这并不是说要更新所有客户端的意义上更好。因为一旦IP地址变化,你就需要更新所有客户端。 - Frederick Nord

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接