/* $OpenBSD$ */ /* * Copyright (c) 2016 Renato Westphal * Copyright (c) 2009 Michele Marchetto * Copyright (c) 2005 Claudio Jeker * Copyright (c) 2003, 2004 Henning Brauer * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include "ldpd.h" #include "ldpe.h" #include "log.h" #include "lib/log.h" #include "privs.h" #include "sockopt.h" extern struct zebra_privs_t ldpd_privs; extern struct zebra_privs_t ldpe_privs; int ldp_create_socket(int af, enum socket_type type) { int fd, domain, proto; union ldpd_addr addr; union sockunion local_su; #ifdef __OpenBSD__ int opt; #endif /* create socket */ switch (type) { case LDP_SOCKET_DISC: case LDP_SOCKET_EDISC: domain = SOCK_DGRAM; proto = IPPROTO_UDP; break; case LDP_SOCKET_SESSION: domain = SOCK_STREAM; proto = IPPROTO_TCP; break; default: fatalx("ldp_create_socket: unknown socket type"); } fd = socket(af, domain, proto); if (fd == -1) { log_warn("%s: error creating socket", __func__); return (-1); } sock_set_nonblock(fd); sockopt_v6only(af, fd); /* bind to a local address/port */ switch (type) { case LDP_SOCKET_DISC: /* listen on all addresses */ memset(&addr, 0, sizeof(addr)); addr2sa(af, &addr, LDP_PORT, &local_su); break; case LDP_SOCKET_EDISC: case LDP_SOCKET_SESSION: addr = (ldp_af_conf_get(ldpd_conf, af))->trans_addr; addr2sa(af, &addr, LDP_PORT, &local_su); /* ignore any possible error */ sock_set_bindany(fd, 1); break; } frr_with_privs(&ldpd_privs) { if (sock_set_reuse(fd, 1) == -1) { close(fd); return (-1); } if (bind(fd, &local_su.sa, sockaddr_len(&local_su.sa)) == -1) { log_warnx("%s: error binding socket: %s", __func__, safe_strerror(errno)); close(fd); return (-1); } } /* set options */ switch (af) { case AF_INET: if (sock_set_ipv4_tos(fd, IPTOS_PREC_INTERNETCONTROL) == -1) { close(fd); return (-1); } if (type == LDP_SOCKET_DISC) { if (sock_set_ipv4_mcast_ttl(fd, IP_DEFAULT_MULTICAST_TTL) == -1) { close(fd); return (-1); } if (sock_set_ipv4_mcast_loop(fd) == -1) { close(fd); return (-1); } } if (type == LDP_SOCKET_DISC || type == LDP_SOCKET_EDISC) { if (sock_set_ipv4_recvif(fd, 1) == -1) { close(fd); return (-1); } #ifndef MSG_MCAST #if defined(HAVE_IP_PKTINFO) if (sock_set_ipv4_pktinfo(fd, 1) == -1) { close(fd); return (-1); } #elif defined(HAVE_IP_RECVDSTADDR) if (sock_set_ipv4_recvdstaddr(fd, 1) == -1) { close(fd); return (-1); } #else #error "Unsupported socket API" #endif #endif /* MSG_MCAST */ } if (type == LDP_SOCKET_SESSION) { if (sock_set_ipv4_ucast_ttl(fd, 255) == -1) { close(fd); return (-1); } } break; case AF_INET6: if (sock_set_ipv6_dscp(fd, IPTOS_PREC_INTERNETCONTROL) == -1) { close(fd); return (-1); } if (type == LDP_SOCKET_DISC) { if (sock_set_ipv6_mcast_loop(fd) == -1) { close(fd); return (-1); } if (sock_set_ipv6_mcast_hops(fd, 255) == -1) { close(fd); return (-1); } if (!(ldpd_conf->ipv6.flags & F_LDPD_AF_NO_GTSM)) { /* ignore any possible error */ sock_set_ipv6_minhopcount(fd, 255); } } if (type == LDP_SOCKET_DISC || type == LDP_SOCKET_EDISC) { if (sock_set_ipv6_pktinfo(fd, 1) == -1) { close(fd); return (-1); } } if (type == LDP_SOCKET_SESSION) { if (sock_set_ipv6_ucast_hops(fd, 255) == -1) { close(fd); return (-1); } } break; } switch (type) { case LDP_SOCKET_DISC: case LDP_SOCKET_EDISC: sock_set_recvbuf(fd); break; case LDP_SOCKET_SESSION: if (listen(fd, LDP_BACKLOG) == -1) log_warn("%s: error listening on socket", __func__); #ifdef __OpenBSD__ opt = 1; if (setsockopt(fd, IPPROTO_TCP, TCP_MD5SIG, &opt, sizeof(opt)) == -1) { if (errno == ENOPROTOOPT) { /* system w/o md5sig */ log_warnx("md5sig not available, disabling"); sysdep.no_md5sig = 1; } else { close(fd); return (-1); } } #endif break; } return (fd); } void sock_set_nonblock(int fd) { int flags; if ((flags = fcntl(fd, F_GETFL, 0)) == -1) fatal("fcntl F_GETFL"); flags |= O_NONBLOCK; if (fcntl(fd, F_SETFL, flags) == -1) fatal("fcntl F_SETFL"); } void sock_set_cloexec(int fd) { int flags; if ((flags = fcntl(fd, F_GETFD, 0)) == -1) fatal("fcntl F_GETFD"); flags |= FD_CLOEXEC; if (fcntl(fd, F_SETFD, flags) == -1) fatal("fcntl F_SETFD"); } void sock_set_recvbuf(int fd) { int bsize; bsize = 65535; while (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &bsize, sizeof(bsize)) == -1) bsize /= 2; } int sock_set_reuse(int fd, int enable) { if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) { log_warn("%s: error setting SO_REUSEADDR", __func__); return (-1); } return (0); } int sock_set_bindany(int fd, int enable) { #ifdef HAVE_SO_BINDANY frr_with_privs(&ldpd_privs) { if (setsockopt(fd, SOL_SOCKET, SO_BINDANY, &enable, sizeof(int)) < 0) { log_warn("%s: error setting SO_BINDANY", __func__); return (-1); } } return (0); #elif defined(HAVE_IP_FREEBIND) if (setsockopt(fd, IPPROTO_IP, IP_FREEBIND, &enable, sizeof(int)) < 0) { log_warn("%s: error setting IP_FREEBIND", __func__); return (-1); } return (0); #elif defined(IP_BINDANY) frr_with_privs(&ldpd_privs) { if (setsockopt(fd, IPPROTO_IP, IP_BINDANY, &enable, sizeof(int)) < 0) { log_warn("%s: error setting IP_BINDANY", __func__); return (-1); } } return (0); #else log_warnx( "%s: missing SO_BINDANY, IP_FREEBIND and IP_BINDANY, unable to bind to a nonlocal IP address", __func__); return (-1); #endif /* HAVE_SO_BINDANY */ } #ifndef __OpenBSD__ /* * Set MD5 key for the socket, for the given peer address. If the password * is NULL or zero-length, the option will be disabled. */ int sock_set_md5sig(int fd, int af, union ldpd_addr *addr, const char *password) { int ret = -1; int save_errno = ENOSYS; #if HAVE_DECL_TCP_MD5SIG union sockunion su; #endif if (fd == -1) return (0); #if HAVE_DECL_TCP_MD5SIG addr2sa(af, addr, 0, &su); frr_with_privs(&ldpe_privs) { ret = sockopt_tcp_signature(fd, &su, password); save_errno = errno; } #endif /* HAVE_TCP_MD5SIG */ if (ret < 0) log_warnx("%s: can't set TCP_MD5SIG option on fd %d: %s", __func__, fd, safe_strerror(save_errno)); return (ret); } #endif int sock_set_ipv4_tos(int fd, int tos) { if (setsockopt(fd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) < 0) { log_warn("%s: error setting IP_TOS to 0x%x", __func__, tos); return (-1); } return (0); } int sock_set_ipv4_recvif(int fd, ifindex_t enable) { return (setsockopt_ifindex(AF_INET, fd, enable)); } int sock_set_ipv4_minttl(int fd, int ttl) { return (sockopt_minttl(AF_INET, fd, ttl)); } int sock_set_ipv4_ucast_ttl(int fd, int ttl) { if (setsockopt(fd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)) < 0) { log_warn("%s: error setting IP_TTL", __func__); return (-1); } return (0); } int sock_set_ipv4_mcast_ttl(int fd, uint8_t ttl) { if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, (char *)&ttl, sizeof(ttl)) < 0) { log_warn("%s: error setting IP_MULTICAST_TTL to %d", __func__, ttl); return (-1); } return (0); } #ifndef MSG_MCAST #if defined(HAVE_IP_PKTINFO) int sock_set_ipv4_pktinfo(int fd, int enable) { if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &enable, sizeof(enable)) < 0) { log_warn("%s: error setting IP_PKTINFO", __func__); return (-1); } return (0); } #elif defined(HAVE_IP_RECVDSTADDR) int sock_set_ipv4_recvdstaddr(int fd, int enable) { if (setsockopt(fd, IPPROTO_IP, IP_RECVDSTADDR, &enable, sizeof(enable)) < 0) { log_warn("%s: error setting IP_RECVDSTADDR", __func__); return (-1); } return (0); } #else #error "Unsupported socket API" #endif #endif /* MSG_MCAST */ int sock_set_ipv4_mcast(struct iface *iface) { struct in_addr if_addr; if_addr.s_addr = if_get_ipv4_addr(iface); if (setsockopt_ipv4_multicast_if(global.ipv4.ldp_disc_socket, if_addr, iface->ifindex) < 0) { log_warn("%s: error setting IP_MULTICAST_IF, interface %s", __func__, iface->name); return (-1); } return (0); } int sock_set_ipv4_mcast_loop(int fd) { return (setsockopt_ipv4_multicast_loop(fd, 0)); } int sock_set_ipv6_dscp(int fd, int dscp) { if (setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &dscp, sizeof(dscp)) < 0) { log_warn("%s: error setting IPV6_TCLASS", __func__); return (-1); } return (0); } int sock_set_ipv6_pktinfo(int fd, int enable) { if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &enable, sizeof(enable)) < 0) { log_warn("%s: error setting IPV6_RECVPKTINFO", __func__); return (-1); } return (0); } int sock_set_ipv6_minhopcount(int fd, int hoplimit) { return (sockopt_minttl(AF_INET6, fd, hoplimit)); } int sock_set_ipv6_ucast_hops(int fd, int hoplimit) { if (setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &hoplimit, sizeof(hoplimit)) < 0) { log_warn("%s: error setting IPV6_UNICAST_HOPS", __func__); return (-1); } return (0); } int sock_set_ipv6_mcast_hops(int fd, int hoplimit) { if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &hoplimit, sizeof(hoplimit)) < 0) { log_warn("%s: error setting IPV6_MULTICAST_HOPS", __func__); return (-1); } return (0); } int sock_set_ipv6_mcast(struct iface *iface) { if (setsockopt(global.ipv6.ldp_disc_socket, IPPROTO_IPV6, IPV6_MULTICAST_IF, &iface->ifindex, sizeof(iface->ifindex)) < 0) { log_warn("%s: error setting IPV6_MULTICAST_IF, interface %s", __func__, iface->name); return (-1); } return (0); } int sock_set_ipv6_mcast_loop(int fd) { unsigned int loop = 0; if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &loop, sizeof(loop)) < 0) { log_warn("%s: error setting IPV6_MULTICAST_LOOP", __func__); return (-1); } return (0); }