简体   繁体   English

sendmsg() / connect() 因原始 IPv6 套接字上的权限错误/EACCES 而失败

[英]sendmsg() / connect() fails with permission error/EACCES on raw IPv6 socket

I try to get a program running on OpenWRT (kernel ver.: 4.14.215, musl libc ver.: 1.1.24) which implements RFC8157 (a new tunneling protocol).我尝试让一个程序在 OpenWRT(内核版本:4.14.215,musl libc 版本:1.1.24)上运行,它实现了RFC8157 (一种新的隧道协议)。 Unfortunately the guy who wrote it doesn't seem to maintain it anymore.不幸的是,写它的人似乎不再维护它了。

At some point it writes its first message to a raw ipv6 socket via sendmsg() .在某些时候,它通过sendmsg()将其第一条消息写入原始 ipv6 套接字。 Unfortunately sendmsg() returns EACCES.不幸的是 sendmsg() 返回 EACCES。 I am pretty new to system programming and do not really have a clue what to look for.我对系统编程很陌生,并不知道要寻找什么。

I have tried the following:我尝试了以下方法:

#> ls -l /proc/[pid]/fd/*
lrwx------    1 root     root            64 Jan 25 17:41 /proc/22727/fd/0 -> /dev/pts/0
lrwx------    1 root     root            64 Jan 25 17:41 /proc/22727/fd/1 -> /dev/pts/0
lrwx------    1 root     root            64 Jan 25 17:41 /proc/22727/fd/2 -> /dev/pts/0
lrwx------    1 root     root            64 Jan 25 17:41 /proc/22727/fd/3 -> socket:[1293688]

#> ls -l /proc/[pid]/fdinfo/*
pos:    0
flags:  02
mnt_id: 8

So the socket seems to be opened in read/write mode.所以套接字似乎以读/写模式打开。

lsof lists the socket as well. lsof 也列出了套接字。 But for some reason with an ipv6 address of 0.但由于某种原因,ipv6 地址为 0。

#> lsof | grep [pid]
openhybri 18018                      root    3u     raw6                 0t0      92469 00000000000000000000000000000000:002F->00000000000000000000000000000000:0000 st=07

The man page lists the attempt to send an UDP packet from a broadcast address to an anycast address as possible cause.手册页列出了尝试将 UDP 数据包从广播地址发送到任播地址的可能原因。 But this seems not the case here.但这似乎不是这里的情况。 A raw IPv6 socket is not a UDP socket (isn't it?) and the src IP is a public one.原始 IPv6 套接字不是 UDP 套接字(不是吗?),src IP 是公共的。

Everything is executed as root user.一切都以root用户身份执行。

#> id
uid=0(root) gid=0(root) groups=0(root)

As I am not really sure what to look for, here is the whole function:因为我不太确定要找什么,这里是整个 function:

sendmsg() is used in the last if statement. sendmsg()用于最后一个 if 语句。

bool send_grecpmessage(uint8_t msgtype, uint8_t tuntype, void *attributes, int attributes_size) {
    unsigned char buffer[MAX_PKT_SIZE] = {};
    int size = 0;

    /* GRE header */
    struct grehdr *greh = (struct grehdr *)(buffer + size);
    greh->flags_and_version = htons(GRECP_FLAGSANDVERSION);
    greh->proto = htons(GRECP_PROTO);
    greh->key = htonl(runtime.haap.bonding_key);
    size += sizeof(struct grehdr);

    /* GRECP header */
    struct grecphdr *grecph = (struct grecphdr *)(buffer + size);
    grecph->msgtype_and_tuntype = (msgtype << 4) | tuntype;
    size += sizeof(struct grecphdr);

    /* Add GRECP attributes */
    memcpy(buffer + size, attributes, attributes_size);
    size += attributes_size;

    /* Source & Destination */
    struct sockaddr_in6 src = {};
    src.sin6_family = AF_INET6;
    if (tuntype == GRECP_TUNTYPE_LTE) {
        src.sin6_addr = runtime.lte.interface_ip;
    } else {
        src.sin6_addr = runtime.dsl.interface_ip;
    }
    struct sockaddr_in6 dst = {};
    dst.sin6_family = AF_INET6;
    dst.sin6_addr = runtime.haap.ip;

    /* Construct control information */
    struct msghdr msgh = {};
    struct iovec msgiov = {};
    struct cmsghdr *c;
    struct unp_in_pktinfo {
        struct in6_addr ipi6_addr;
        int ipi6_ifindex;
    } *pi;
    msgh.msg_name = &dst;
    msgh.msg_namelen = sizeof(struct sockaddr_in6);
    msgiov.iov_base = buffer;
    msgiov.iov_len = size;
    msgh.msg_iov = &msgiov;
    msgh.msg_iovlen = 1;
    unsigned char control_buf[CMSG_LEN(sizeof(struct unp_in_pktinfo))] = {};
    msgh.msg_control = &control_buf;
    msgh.msg_controllen = CMSG_LEN(sizeof(struct unp_in_pktinfo));
    c = CMSG_FIRSTHDR(&msgh);
    c->cmsg_level = IPPROTO_IPV6;
    c->cmsg_type = IPV6_PKTINFO;
    c->cmsg_len = CMSG_LEN(sizeof(struct unp_in_pktinfo));
    pi = (struct unp_in_pktinfo *)CMSG_DATA(c);
    pi->ipi6_addr = src.sin6_addr;
    msgh.msg_controllen = c->cmsg_len;

    bool res = true;
    if (memcmp(&src.sin6_addr, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 16) != 0) {
        if (sendmsg(sockfd, &msgh, 0) <= 0) {
            logger(LOG_ERROR, "Raw socket send failed: %s\n", strerror(errno));
            res = false;
        }
    } else {
        /* if we don't set a source ip, sendmsg() will use the ip of the outgoing interface
        ** and since the haap doesn't verify source ip's we would still get replies for our hellos
        */
        res = false;
    }

    /* TODO: check if sending failed due to a link failure and call send_grecpnotify_linkfailure if it did */

    return res;
}

You need root (or a sufficient subset as capabilities) to perform raw packet io.您需要 root(或足够的子集作为功能)来执行原始数据包 io。 This is because ability to construct and send or capture arbitrary packets allows you to spoof or intercept traffic that's part of a connection belonging to another user or system network facilities.这是因为构造和发送或捕获任意数据包的能力允许您欺骗或拦截属于另一个用户或系统网络设施的连接的一部分的流量。 EACCES is telling you that you don't have sufficient permissions. EACCES告诉您您没有足够的权限。

The problem was that the ip route wasn't set for the specific address I tried to reach.问题是没有为我试图到达的特定地址设置 ip 路由。

ip route add [IP] via [gateway] dev [interface]

solved it.解决了。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM