Tab*_*Eye 5 c sockets linux networking udp
我需要在linux上重新创建一个服务,该服务曾经在运行LwIP堆栈(轻量级IP)的嵌入式系统上运行.
该服务使用UDP广播INADDR_BROADCAST(255.255.255.255)来查找和配置同一物理子网上的设备.它发送"扫描",运行此服务的所有设备都会回复其完整的网络设置(所有NIC,所有的MAC和IP).然后,用户获取这些设备的列表,并可以更改IP设置(使用现有协议).
[是的,我知道人们使用DHCP进行此操作,但我们在这里讨论的是工业部门,协议/服务已经存在,所以我别无选择,只能实现兼容的东西]
由于设备有多个NIC,我需要能够接收此广播,知道哪个NIC接收到它并通过该NIC发送回复.此外,该服务是可配置的,因此它不会在特定NIC上打开套接字.
LwIP堆栈没有Linux堆栈那么复杂,因此绑定到IP的套接字仍会接收所有数据包INADDR_BROADCAST.因此,实现这一点非常简单.
在Linux上,我想我有几个选项可以做到这一点:
SO_BROADCAST和SO_BINDTODEVICE,所以我bind()他们INADDR_ANY和接收广播.当我通过该套接字发送回复时,将忽略Linux路由并通过所需的NIC发送.root......INADDR_ANY绑定套接字(可能IP_PKTINFO很容易知道数据包到达哪个网卡),每个网卡有一个套接字,绑定到有效地址,SO_BROADCAST并通过这些发送回复.如果我这样走,我想确保发送套接字永远不会收到任何东西(因为我从不在它们上面调用recv().资源饥饿?).SO_RCVBUFSIZE = 0就足够了?实现这个的正确方法是什么?
您可以使用以下命令安装二进制文件CAP_NET_RAW(CAP_NET_BIND_SERVICE如果使用端口 \xe2\x89\xa4 1024);setcap \'cap_net_raw=ep\' yourdaemon作为根。对于IP,SO_BROADCAST不需要任何能力(特别CAP_NET_BROADCAST是不用于IP)。
(有关所需的确切功能,请参阅Linux 内核源代码中的net/core/sock.c:sock_setbindtodevice()、net/core/sock.c:sock_setsockopt()和include/net/sock.h:sock_set_flag()以供验证。)
\n\n然而,守护进程通常以 root 身份启动。在这里,上述内容还不够,因为更改进程的用户 ID(以删除权限)也会清除有效功能。然而,我也更喜欢我的服务以有限的权限运行。
\n\n我会在两种基本方法之间进行选择:
\n\n要求守护进程由 root 执行,或者具有CAP_NET_RAW(可选CAP_NET_BIND_SERVICE)功能。
使用libcap 中的prctl()、setgroups()、initgroups()、setresuid()、和、 、 、和通过切换到专用用户和组来放弃权限,但仅保留(和可选)功能和它们。setresgid()cap_init()cap_set_flag()cap_set_proc()CAP_NET_RAWCAP_NET_BIND_SERVICE
这允许守护进程在不完全重新启动的情况下响应例如 HUP 信号,因为它具有枚举接口和读取其自己的配置文件以打开新接口的套接字的必要权限。
使用特权“加载程序”,打开所有必要的套接字,放弃特权,并执行实际的守护进程。
\n\n守护进程应该以命令行参数的形式或者通过标准输入获取套接字和接口详细信息。该守护进程完全没有特权。
\n\n不幸的是,如果打开新接口,或者配置发生更改,守护进程除了退出之外无法做太多事情。(它甚至无法执行特权加载程序,因为特权已经被删除。)
第一种方法比较常见,在实践中也更容易实现;特别是如果守护进程只能由 root 执行的话。(请记住,守护进程可以响应配置更改,因为它具有必要的功能,但通常没有 root 权限。)我只对我不信任的“黑匣子”二进制文件使用了第二种方法。
\n\n这是一些示例代码。
\n\nprivileges.h:\n #ifndef PRIVILEGES_H\n #define PRIVILEGES_H
#define NEED_CAP_NET_ADMIN (1U << 0)\n#define NEED_CAP_NET_BIND_SERVICE (1U << 1)\n#define NEED_CAP_NET_RAW (1U << 2)\n\nextern int drop_privileges(const char *const user, const unsigned int capabilities);\n\n#endif /* PRIVILEGES_H */\nRun Code Online (Sandbox Code Playgroud)\n\nprivileges.c:
#define _GNU_SOURCE\n#define _BSD_SOURCE\n#include <stdlib.h>\n#include <unistd.h>\n#include <sys/types.h>\n#include <sys/capability.h>\n#include <sys/prctl.h>\n#include <errno.h>\n#include <pwd.h>\n#include <grp.h>\n#include "privileges.h"\n\n/* Only three NEED_CAP_ constants defined. */\n#define MAX_CAPABILITIES 3\n\nstatic int permit_effective(cap_t caps, const unsigned int capabilities)\n{\n cap_value_t value[MAX_CAPABILITIES];\n int values = 0;\n\n if (capabilities & NEED_CAP_NET_ADMIN)\n value[values++] = CAP_NET_ADMIN;\n\n if (capabilities & NEED_CAP_NET_BIND_SERVICE)\n value[values++] = CAP_NET_BIND_SERVICE;\n\n if (capabilities & NEED_CAP_NET_RAW)\n value[values++] = CAP_NET_RAW;\n\n if (values < 1)\n return 0;\n\n if (cap_set_flag(caps, CAP_PERMITTED, values, value, CAP_SET) == -1)\n return errno;\n if (cap_set_flag(caps, CAP_EFFECTIVE, values, value, CAP_SET) == -1)\n return errno;\n\n return 0;\n}\n\nstatic int add_privileges(cap_t caps)\n{\n cap_value_t value[3] = { CAP_SETPCAP, CAP_SETUID, CAP_SETGID };\n\n if (cap_set_flag(caps, CAP_PERMITTED, sizeof value / sizeof value[0], value, CAP_SET) == -1)\n return errno;\n\n if (cap_set_flag(caps, CAP_EFFECTIVE, sizeof value / sizeof value[0], value, CAP_SET) == -1)\n return errno;\n\n return 0;\n}\n\nint drop_privileges(const char *const user, const unsigned int capabilities)\n{\n uid_t uid;\n gid_t gid;\n cap_t caps;\n\n /* Make sure user is neither NULL nor empty. */\n if (!user || !user[0])\n return errno = EINVAL;\n\n /* Find the user. */\n {\n struct passwd *pw;\n\n pw = getpwnam(user);\n if (!pw\n#ifdef UID_MIN\n || pw->pw_uid < (uid_t)UID_MIN\n#endif\n#ifdef UID_MAX\n || pw->pw_uid > (uid_t)UID_MAX\n#endif\n#ifdef GID_MIN\n || pw->pw_gid < (gid_t)GID_MIN\n#endif\n#ifdef GID_MAX\n || pw->pw_gid > (gid_t)GID_MAX\n#endif\n )\n return errno = EINVAL;\n\n uid = pw->pw_uid;\n gid = pw->pw_gid;\n\n endpwent();\n }\n\n /* Install privileged capabilities. */\n caps = cap_init();\n if (!caps)\n return errno = ENOMEM;\n if (permit_effective(caps, capabilities)) {\n const int cause = errno;\n cap_free(caps);\n return errno = cause;\n }\n if (add_privileges(caps)) {\n const int cause = errno;\n cap_free(caps);\n return errno = cause;\n }\n if (cap_set_proc(caps) == -1) {\n const int cause = errno;\n cap_free(caps);\n return errno = cause;\n }\n cap_free(caps);\n\n /* Retain permitted capabilities over the identity change. */\n prctl(PR_SET_KEEPCAPS, 1UL, 0UL,0UL,0UL);\n\n if (setresgid(gid, gid, gid) == -1)\n return errno = EPERM;\n\n if (initgroups(user, gid) == -1)\n return errno = EPERM;\n\n if (setresuid(uid, uid, uid) == -1)\n return errno = EPERM;\n\n /* Install unprivileged capabilities. */\n caps = cap_init();\n if (!caps)\n return errno = ENOMEM;\n if (permit_effective(caps, capabilities)) {\n const int cause = errno;\n cap_free(caps);\n return errno = cause;\n }\n if (cap_set_proc(caps) == -1) {\n const int cause = errno;\n cap_free(caps);\n return errno = cause;\n }\n cap_free(caps);\n\n /* Reset standard KEEPCAPS behaviour. */\n prctl(PR_SET_KEEPCAPS, 0UL, 0UL,0UL,0UL);\n\n /* Done. */\n return 0;\n}\nRun Code Online (Sandbox Code Playgroud)\n\nudp-broadcast.h:
#ifndef UDP_BROADCAST_H\n#define UDP_BROADCAST_H\n#include <stdlib.h>\n#include <sys/socket.h>\n#include <netinet/in.h>\n\nstruct udp_socket {\n struct sockaddr_in broadcast; /* Broadcast address */\n unsigned int if_index; /* Interface index */\n int descriptor; /* Socket descriptor */\n};\n\nextern int open_udp_broadcast(struct udp_socket *const udpsocket,\n const char *const interface,\n int const port);\n\nextern int udp_broadcast(const struct udp_socket *const udpsocket,\n const void *const data,\n const size_t size,\n const int flags);\n\nextern size_t udp_receive(const struct udp_socket *const udpsocket,\n void *const data,\n const size_t size_max,\n const int flags,\n struct sockaddr_in *const from_addr,\n struct sockaddr_in *const to_addr,\n struct sockaddr_in *const hdr_addr,\n unsigned int *const if_index);\n\n#endif /* UDP_BROADCAST_H */\nRun Code Online (Sandbox Code Playgroud)\n\nudp-broadcast.c:
#include <unistd.h>\n#include <string.h>\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <net/if.h>\n#include <errno.h>\n#include "udp-broadcast.h"\n\n\nint udp_broadcast(const struct udp_socket *const udpsocket,\n const void *const data,\n const size_t size,\n const int flags)\n{\n ssize_t n;\n\n if (!udpsocket || udpsocket->broadcast.sin_family != AF_INET)\n return errno = EINVAL;\n\n if (!data || size < 1)\n return 0;\n\n n = sendto(udpsocket->descriptor, data, size, flags,\n (const struct sockaddr *)&(udpsocket->broadcast),\n sizeof (struct sockaddr_in));\n\n if (n == (ssize_t)-1)\n return errno;\n if (n == (ssize_t)size)\n return 0;\n return errno = EIO; \n}\n\n\nsize_t udp_receive(const struct udp_socket *const udpsocket,\n void *const data,\n const size_t size_max,\n const int flags,\n struct sockaddr_in *const from_addr,\n struct sockaddr_in *const to_addr,\n struct sockaddr_in *const hdr_addr,\n unsigned int *const if_index)\n{\n char ancillary[512];\n struct msghdr msg;\n struct iovec iov[1];\n struct cmsghdr *cmsg;\n ssize_t n;\n\n if (!data || size_max < 1 || !udpsocket) {\n errno = EINVAL;\n return (size_t)0;\n }\n\n /* Clear results, just in case. */\n if (from_addr) {\n memset(from_addr, 0, sizeof *from_addr);\n from_addr->sin_family = AF_UNSPEC;\n }\n if (to_addr) {\n memset(to_addr, 0, sizeof *to_addr);\n to_addr->sin_family = AF_UNSPEC;\n }\n if (hdr_addr) {\n memset(hdr_addr, 0, sizeof *hdr_addr);\n hdr_addr->sin_family = AF_UNSPEC;\n }\n if (if_index)\n *if_index = 0U;\n\n iov[0].iov_base = data;\n iov[0].iov_len = size_max;\n\n if (from_addr) {\n msg.msg_name = from_addr;\n msg.msg_namelen = sizeof (struct sockaddr_in);\n } else {\n msg.msg_name = NULL;\n msg.msg_namelen = 0;\n }\n\n msg.msg_iov = iov;\n msg.msg_iovlen = 1;\n\n msg.msg_control = ancillary;\n msg.msg_controllen = sizeof ancillary;\n\n msg.msg_flags = 0;\n\n n = recvmsg(udpsocket->descriptor, &msg, flags);\n if (n == (ssize_t)-1)\n return (size_t)0; /* errno set by recvmsg(). */\n if (n < (ssize_t)1) {\n errno = EIO;\n return (size_t)0;\n }\n\n /* Populate data from ancillary message, if requested. */\n if (to_addr || hdr_addr || if_index)\n for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg))\n if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) {\n const struct in_pktinfo *const info = CMSG_DATA(cmsg);\n if (!info)\n continue;\n if (if_index)\n *if_index = info->ipi_ifindex;\n if (to_addr) {\n to_addr->sin_family = AF_INET;\n to_addr->sin_port = udpsocket->broadcast.sin_port; /* This is a guess. */\n to_addr->sin_addr = info->ipi_spec_dst;\n }\n if (hdr_addr) {\n hdr_addr->sin_family = AF_INET;\n hdr_addr->sin_port = udpsocket->broadcast.sin_port; /* A guess, again. */\n hdr_addr->sin_addr = info->ipi_addr;\n }\n }\n\n errno = 0;\n return (size_t)n;\n}\n\nint open_udp_broadcast(struct udp_socket *const udpsocket,\n const char *const interface,\n int const port)\n{\n const size_t interface_len = (interface) ? strlen(interface) : 0;\n const int set_flag = 1;\n int sockfd;\n\n if (udpsocket) {\n memset(udpsocket, 0, sizeof *udpsocket);\n udpsocket->broadcast.sin_family = AF_INET;\n udpsocket->broadcast.sin_addr.s_addr = INADDR_BROADCAST;\n if (port >= 1 && port <= 65535)\n udpsocket->broadcast.sin_port = htons(port);\n udpsocket->descriptor = -1;\n }\n\n if (!udpsocket || interface_len < 1 || port < 1 || port > 65535)\n return errno = EINVAL;\n\n /* Generic UDP socket. */\n sockfd = socket(AF_INET, SOCK_DGRAM, 0);\n if (sockfd == -1)\n return errno;\n\n /* Set SO_REUSEADDR if possible. */\n setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &set_flag, sizeof set_flag);\n\n /* Set IP_FREEBIND if possible. */\n setsockopt(sockfd, IPPROTO_IP, IP_FREEBIND, &set_flag, sizeof set_flag);\n\n /* We need broadcast capability. */\n if (setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &set_flag, sizeof set_flag) == -1) {\n const int real_errno = errno;\n close(sockfd);\n return errno = real_errno;\n }\n\n /* We want the IP_PKTINFO ancillary messages, to determine target address\n * and interface index. */ \n if (setsockopt(sockfd, IPPROTO_IP, IP_PKTINFO, &set_flag, sizeof set_flag) == -1) {\n const int real_errno = errno;\n close(sockfd);\n return errno = real_errno;\n }\n\n /* We bind to the broadcast address. */\n if (bind(sockfd, (const struct sockaddr *)&(udpsocket->broadcast), sizeof udpsocket->broadcast) == -1) {\n const int real_errno = errno;\n close(sockfd);\n return errno = real_errno;\n }\n\n /* Finally, we bind to the specified interface. */\n if (setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, interface, interface_len) == -1) {\n const int real_errno = errno;\n close(sockfd);\n return errno = real_errno;\n }\n\n udpsocket->descriptor = sockfd;\n\n udpsocket->if_index = if_nametoindex(interface);\n\n errno = 0;\n return 0;\n}\nRun Code Online (Sandbox Code Playgroud)\n\nmain.c:
#include <stdlib.h>\n#include <unistd.h>\n#include <string.h>\n#include <signal.h>\n#include <stdio.h>\n#include <netdb.h>\n#include <errno.h>\n#include "privileges.h"\n#include "udp-broadcast.h"\n\nstatic volatile sig_atomic_t done_triggered = 0;\nstatic volatile sig_atomic_t reload_triggered = 0;\n\nstatic void done_handler(int signum)\n{\n __sync_bool_compare_and_swap(&done_triggered, (sig_atomic_t)0, (sig_atomic_t)signum);\n}\n\nstatic void reload_handler(int signum)\n{\n __sync_bool_compare_and_swap(&reload_triggered, (sig_atomic_t)0, (sig_atomic_t)signum);\n}\n\nstatic int install_handler(const int signum, void (*handler)(int))\n{\n struct sigaction act;\n memset(&act, 0, sizeof act);\n sigemptyset(&act.sa_mask);\n act.sa_handler = handler;\n act.sa_flags = 0;\n if (sigaction(signum, &act, NULL) == -1)\n return errno;\n return 0;\n}\n\n/* Return 0 if done_triggered or reload_triggered, nonzero otherwise.\n * Always clears reload_triggered.\n*/\nstatic inline int keep_running(void)\n{\n if (done_triggered)\n return 0;\n return !__sync_fetch_and_and(&reload_triggered, (sig_atomic_t)0);\n}\n\nstatic const char *ipv4_address(const void *const addr)\n{\n static char buffer[16];\n char *end = buffer + sizeof buffer;\n unsigned char byte[4];\n\n if (!addr)\n return "(none)";\n\n memcpy(byte, addr, 4);\n\n *(--end) = \'\\0\';\n do {\n *(--end) = \'0\' + (byte[3] % 10);\n byte[3] /= 10U;\n } while (byte[3]);\n *(--end) = \'.\';\n do {\n *(--end) = \'0\' + (byte[2] % 10);\n byte[2] /= 10U;\n } while (byte[2]);\n *(--end) = \'.\';\n do {\n *(--end) = \'0\' + (byte[1] % 10);\n byte[1] /= 10U;\n } while (byte[1]);\n *(--end) = \'.\';\n do {\n *(--end) = \'0\' + (byte[0] % 10);\n byte[0] /= 10U;\n } while (byte[0]);\n\n return (const char *)end;\n}\n\nint main(int argc, char *argv[])\n{\n int port;\n char dummy;\n\n /* Check usage. */\n if (argc != 4 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {\n fprintf(stderr, "\\n");\n fprintf(stderr, "Usage: %s [ -h | --help ]\\n", argv[0]);\n fprintf(stderr, " %s USERNAME INTERFACE PORT\\n", argv[0]);\n fprintf(stderr, "Where:\\n");\n fprintf(stderr, " USERNAME is the unprivileged user to run as,\\n");\n fprintf(stderr, " INTERFACE is the interface to bind to, and\\n");\n fprintf(stderr, " PORT is the UDP/IPv4 port number to use.\\n");\n fprintf(stderr, "\\n");\n return EXIT_FAILURE;\n }\n\n /* Parse the port into a number. */\n if (sscanf(argv[3], "%d %c", &port, &dummy) != 1 || port < 1 || port > 65535) {\n struct servent *serv = getservbyname(argv[3], "udp");\n if (serv && serv->s_port > 1 && serv->s_port < 65536) {\n port = serv->s_port;\n endservent();\n } else {\n endservent();\n fprintf(stderr, "%s: Invalid port.\\n", argv[3]);\n return EXIT_FAILURE;\n }\n }\n\n /* Drop privileges. */\n if (drop_privileges(argv[1], NEED_CAP_NET_RAW)) {\n fprintf(stderr, "%s.\\n", strerror(errno));\n return EXIT_FAILURE;\n }\n\n /* Install signal handlers. */\n if (install_handler(SIGINT, done_handler) ||\n install_handler(SIGTERM, done_handler) ||\n install_handler(SIGHUP, reload_handler) ||\n install_handler(SIGUSR1, reload_handler)) {\n fprintf(stderr, "Cannot install signal handlers: %s.\\n", strerror(errno));\n return EXIT_FAILURE;\n }\n\n fprintf(stderr, "Send a SIGINT (Ctrl+C) or SIGTERM to stop the service:\\n");\n fprintf(stderr, "\\tkill -SIGTERM %ld\\n", (long)getpid());\n fprintf(stderr, "Send a SIGHUP or SIGUSR1 to have the service reload and rebroadcast:\\n");\n fprintf(stderr, "\\tkill -SIGHUP %ld\\n", (long)getpid());\n fprintf(stderr, "Privileges dropped successfully.\\n\\n");\n fflush(stderr);\n\n while (!done_triggered) {\n struct udp_socket s;\n\n if (open_udp_broadcast(&s, argv[2], port)) {\n fprintf(stderr, "%s port %s: %s.\\n", argv[2], argv[3], strerror(errno));\n return EXIT_FAILURE;\n }\n\n if (udp_broadcast(&s, "Hello?", 6, MSG_NOSIGNAL)) {\n fprintf(stderr, "%s port %s: Broadcast failed: %s.\\n", argv[2], argv[3], strerror(errno));\n close(s.descriptor);\n return EXIT_FAILURE;\n }\n\n if (s.if_index)\n fprintf(stderr, "Broadcast sent using interface %s (index %u); waiting for responses.\\n", argv[2], s.if_index);\n else\n fprintf(stderr, "Broadcast sent using interface %s; waiting for responses.\\n", argv[2]);\n fflush(stderr);\n\n while (keep_running()) {\n struct sockaddr_in from_addr, to_addr, hdr_addr;\n unsigned char data[512];\n unsigned int if_index;\n size_t size, i;\n\n size = udp_receive(&s, data, sizeof data, 0, &from_addr, &to_addr, &hdr_addr, &if_index);\n if (size > 0) {\n printf("Received %zu bytes:", size);\n for (i = 0; i < size; i++)\n if (i & 15)\n printf(" %02x", data[i]);\n else\n printf("\\n\\t%02x", data[i]);\n if (if_index)\n printf("\\n\\t Index: %u", if_index);\n printf("\\n\\t From: %s", ipv4_address(&from_addr.sin_addr));\n printf("\\n\\t To: %s", ipv4_address(&to_addr.sin_addr));\n printf("\\n\\tHeader: %s", ipv4_address(&hdr_addr.sin_addr));\n printf("\\n");\n fflush(stdout);\n } else\n if (errno != EINTR) {\n fprintf(stderr, "%s\\n", strerror(errno));\n break;\n }\n }\n\n