如何配置 Apache 以监听不属于接口的单个​​ IPv6 地址?

如何配置 Apache 以监听不属于接口的单个​​ IPv6 地址?

我正在尝试设置多个 Apache SSL 虚拟主机,每个都在不同的 IPv6 地址上。

假设我的 CentOS7 VPS 已分配一个路由 /64 IPv6 块,2001:db8:acac:acac::/64并且我已经可以看到传入的数据包(tcpdump -nn -i eth0 'ip6 and src or dst net 2001:db8:acac:acac::/64'显示数据包正常)。

我知道我可以为 eth0()分配任意数量的单独地址ip -6 addr add 2001:db8:acac:acac::1234 dev eth0,但我想让接口允许应用程序绑定到 2^64 个地址中的任意一个。

根据建议(参见底部的链接),我添加了一条规则(ip -6 rule add from 2001:db8:acac:acac::/64 iif eth0 lookup 200)和一条路由(ip route add local 2001:db8:acac:acac::/64 dev lo table 200),现在我可以使用ping6/64 块中的任何 IP 地址,并且可以使用 /64 块中的任何地址连接到监听通配符的服务(例如:::22ssh)。

问题是:我怎样才能让一个程序绑定到一个单身的/64 块中的地址?由于没有接口拥有该块中的任何地址,因此我在 apache 日志中看到以下内容:

... AH00072: make_sock: could not bind to address [2001:db8:acac:acac::1234]:443

我曾看到过提到IP_TRANSPARENT作为一种可能的解决方案,但在 Apache 源中找不到提到这一点,只有在 中bits/in.h,包括netinet/in.h

有没有人让这个运行过,无论是对于 Apache 还是其他应用程序(特别是:dovecot、postfix、bind)?


在发布此问题之前请阅读相关文章:

答案1

您可以将该地址分配给任何接口。例如,将其分配给 lo(除了 ::1)。IPv6 非常擅长在任何接口上拥有多个地址。然后,在将地址设为本地后,您就可以监听它了。

UPD:在我看来,这个想法与第一个链接中提到的将地址块分配给 lo 的想法没有太大区别。本质上这是相同的,但块退化为单个地址。

答案2

虽然我还没有能够阿帕奇绑定到不属于接口的单个​​地址,我已经制作了一个可以运行的测试 C 程序,它可以绑定到任何 IPv6 地址,无论该地址是否属于接口:

/*
  Test program to bind to an IPv6 address not owned by an interface.
  This code is from public domain sources, and is released into the public domain.
*/

#include <arpa/inet.h>
#include <error.h>
#include <errno.h>
#include <net/if.h>
#include <netinet/in.h> // also includes bits/in.h
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h> // also includes bits/ioctls.h
#include <sys/socket.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>

// A real address to use as a sanity check and make sure the program can 
// bind to an address which *is* owned by an interface
#define REALADDR {{{0x2a,0x00, ...}}}

// A fake address to show the program can bind to an address which is *not* 
// owned by any interface
#define SOMEADDR {{{0x20,0x01, 0x0d,0xb8, 0x00,0x00, 0x00,0x00, \
                    0x00,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x01 }}}

int main(int argc, char *argv[])
{
    struct sockaddr_in6 serv_addr;
    int listenfd = 0, connfd = 0, i;

    char sendBuff[1025];
    time_t ticks;

    listenfd = socket(AF_INET6, SOCK_STREAM, 0);
    printf("socket fd is %d\n", listenfd);
    memset(&serv_addr, 0, sizeof(serv_addr));
    memset(sendBuff, 0, sizeof(sendBuff));

    serv_addr.sin6_family = AF_INET6;
    serv_addr.sin6_port = htons(5000);
    struct in6_addr someaddr = SOMEADDR;
    serv_addr.sin6_addr = someaddr;

    // Here's the magic:
    int opt = 1;
    if (setsockopt(listenfd, SOL_IP, IP_FREEBIND, &opt, sizeof(opt)) < 0)
        error(1, errno, "setsockopt(IP_FREEBIND) failed");

    printf("Binding to ");
    for (i = 0; i < 14; i += 2)
        printf("%x:", (serv_addr.sin6_addr.s6_addr[i] << 8) + 
            serv_addr.sin6_addr.s6_addr[i+1]);
    printf("%x\n", (serv_addr.sin6_addr.s6_addr[14] << 8) + 
        serv_addr.sin6_addr.s6_addr[15]);
    if (bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0)
        error(1, errno, "bind failed");

    if (listen(listenfd, 10) < 0)
        error(1, errno, "listen failed");

    while(1)
    {
        connfd = accept(listenfd, (struct sockaddr*)NULL, NULL);
        printf("accept returned %d\n", connfd);

        // Send some data - the current date and time.
        ticks = time(NULL);
        snprintf(sendBuff, sizeof(sendBuff), "Now is %.24s\r\n", ctime(&ticks));
        write(connfd, sendBuff, strlen(sendBuff));

        close(connfd);
        sleep(1);
     }
}

相关内容