/* * AF_CUST_UDP/AF_CUST_UDP6 UDP protocol layer * * Copyright 2019 HAProxy Technologies, Frédéric Lécaille * * Partial merge by Emeric Brun * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int udp_bind_listener(struct listener *listener, char *errmsg, int errlen); static void udp4_add_listener(struct listener *listener, int port); static void udp6_add_listener(struct listener *listener, int port); /* Note: must not be declared as its list will be overwritten */ static struct protocol proto_udp4 = { .name = "udp4", .sock_domain = AF_CUST_UDP4, .sock_type = SOCK_DGRAM, .sock_prot = IPPROTO_UDP, .sock_family = AF_INET, .sock_addrlen = sizeof(struct sockaddr_in), .l3_addrlen = 32/8, .accept = NULL, .connect = NULL, .bind = sock_inet_bind_receiver, .listen = udp_bind_listener, .enable_all = enable_all_listeners, .get_src = udp_get_src, .get_dst = udp_get_dst, .pause = udp_pause_listener, .add = udp4_add_listener, .addrcmp = sock_inet4_addrcmp, .listeners = LIST_HEAD_INIT(proto_udp4.listeners), .nb_listeners = 0, }; INITCALL1(STG_REGISTER, protocol_register, &proto_udp4); /* Note: must not be declared as its list will be overwritten */ static struct protocol proto_udp6 = { .name = "udp6", .sock_domain = AF_CUST_UDP6, .sock_type = SOCK_DGRAM, .sock_prot = IPPROTO_UDP, .sock_family = AF_INET6, .sock_addrlen = sizeof(struct sockaddr_in6), .l3_addrlen = 128/8, .accept = NULL, .connect = NULL, .bind = sock_inet_bind_receiver, .listen = udp_bind_listener, .enable_all = enable_all_listeners, .get_src = udp6_get_src, .get_dst = udp6_get_dst, .pause = udp_pause_listener, .add = udp6_add_listener, .addrcmp = sock_inet6_addrcmp, .listeners = LIST_HEAD_INIT(proto_udp6.listeners), .nb_listeners = 0, }; INITCALL1(STG_REGISTER, protocol_register, &proto_udp6); /* * Retrieves the source address for the socket , with indicating * if we're a listener (=0) or an initiator (!=0). It returns 0 in case of * success, -1 in case of error. The socket's source address is stored in * for bytes. */ int udp_get_src(int fd, struct sockaddr *sa, socklen_t salen, int dir) { int ret; ret = sock_get_src(fd, sa, salen, dir); if (!ret) sa->sa_family = AF_CUST_UDP4; return ret; } /* * Retrieves the source address for the socket , with indicating * if we're a listener (=0) or an initiator (!=0). It returns 0 in case of * success, -1 in case of error. The socket's source address is stored in * for bytes. */ int udp6_get_src(int fd, struct sockaddr *sa, socklen_t salen, int dir) { int ret; ret = sock_get_src(fd, sa, salen, dir); if (!ret) sa->sa_family = AF_CUST_UDP6; return ret; } /* * Retrieves the original destination address for the socket , with * indicating if we're a listener (=0) or an initiator (!=0). In the case of a * listener, if the original destination address was translated, the original * address is retrieved. It returns 0 in case of success, -1 in case of error. * The socket's source address is stored in for bytes. */ int udp_get_dst(int fd, struct sockaddr *sa, socklen_t salen, int dir) { int ret; ret = sock_inet_get_dst(fd, sa, salen, dir); if (!ret) sa->sa_family = AF_CUST_UDP4; return ret; } /* * Retrieves the original destination address for the socket , with * indicating if we're a listener (=0) or an initiator (!=0). In the case of a * listener, if the original destination address was translated, the original * address is retrieved. It returns 0 in case of success, -1 in case of error. * The socket's source address is stored in for bytes. */ int udp6_get_dst(int fd, struct sockaddr *sa, socklen_t salen, int dir) { int ret; ret = sock_get_dst(fd, sa, salen, dir); if (!ret) sa->sa_family = AF_CUST_UDP6; return ret; } /* This function tries to bind a UDPv4/v6 listener. It may return a warning or * an error message in if the message is at most bytes long * (including '\0'). Note that may be NULL if is also zero. * The return value is composed from ERR_ABORT, ERR_WARN, * ERR_ALERT, ERR_RETRYABLE and ERR_FATAL. ERR_NONE indicates that everything * was alright and that no message was returned. ERR_RETRYABLE means that an * error occurred but that it may vanish after a retry (eg: port in use), and * ERR_FATAL indicates a non-fixable error. ERR_WARN and ERR_ALERT do not alter * the meaning of the error, but just indicate that a message is present which * should be displayed with the respective level. Last, ERR_ABORT indicates * that it's pointless to try to start other listeners. No error message is * returned if errlen is NULL. */ int udp_bind_listener(struct listener *listener, char *errmsg, int errlen) { __label__ udp_return, udp_close_return; int fd, err; const char *msg = NULL; /* copy listener addr because sometimes we need to switch family */ struct sockaddr_storage addr_inet = listener->rx.addr; /* force to classic sock family */ addr_inet.ss_family = listener->rx.proto->sock_family; /* ensure we never return garbage */ if (errlen) *errmsg = 0; if (listener->state != LI_ASSIGNED) return ERR_NONE; /* already bound */ err = ERR_NONE; if (listener->rx.flags & RX_F_BOUND) goto bound; /* TODO: Implement reuse fd. Take care that to identify fd to reuse * listeners uses a special AF_CUST_ family and we MUST consider * IPPROTO (sockaddr is not enough) */ fd = my_socketat(listener->rx.settings->netns, listener->rx.proto->sock_family, listener->rx.proto->sock_type, listener->rx.proto->sock_prot); if (fd == -1) { err |= ERR_RETRYABLE | ERR_ALERT; msg = "cannot create listening socket"; goto udp_return; } if (fd >= global.maxsock) { err |= ERR_FATAL | ERR_ABORT | ERR_ALERT; msg = "not enough free sockets (raise '-n' parameter)"; goto udp_close_return; } if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) { err |= ERR_FATAL | ERR_ALERT; msg = "cannot make socket non-blocking"; goto udp_close_return; } if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) == -1) { /* not fatal but should be reported */ msg = "cannot do so_reuseaddr"; err |= ERR_ALERT; } #ifdef SO_REUSEPORT /* OpenBSD and Linux 3.9 support this. As it's present in old libc versions of * Linux, it might return an error that we will silently ignore. */ if (global.tune.options & GTUNE_USE_REUSEPORT) setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)); #endif if (listener->rx.settings->options & RX_O_FOREIGN) { switch (addr_inet.ss_family) { case AF_INET: if (!sock_inet4_make_foreign(fd)) { msg = "cannot make listening socket transparent"; err |= ERR_ALERT; } break; case AF_INET6: if (!sock_inet6_make_foreign(fd)) { msg = "cannot make listening socket transparent"; err |= ERR_ALERT; } break; } } #ifdef SO_BINDTODEVICE /* Note: this might fail if not CAP_NET_RAW */ if (listener->rx.settings->interface) { if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, listener->rx.settings->interface, strlen(listener->rx.settings->interface) + 1) == -1) { msg = "cannot bind listener to device"; err |= ERR_WARN; } } #endif #if defined(IPV6_V6ONLY) if (listener->rx.addr.ss_family == AF_INET6) { /* Prepare to match the v6only option against what we really want. Note * that sadly the two options are not exclusive to each other and that * v6only is stronger than v4v6. */ if ((listener->rx.settings->options & RX_O_V6ONLY) || (sock_inet6_v6only_default && !(listener->rx.settings->options & RX_O_V4V6))) setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); else setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &zero, sizeof(zero)); } #endif if (bind(fd, (struct sockaddr *)&addr_inet, listener->rx.proto->sock_addrlen) < 0) { err |= ERR_RETRYABLE | ERR_ALERT; msg = "cannot bind socket"; goto udp_close_return; } listener->rx.flags |= RX_F_BOUND; bound: /* the socket is ready */ listener->rx.fd = fd; listener->state = LI_LISTEN; if (listener->bind_conf->frontend->mode == PR_MODE_SYSLOG) fd_insert(fd, listener, syslog_fd_handler, thread_mask(listener->rx.settings->bind_thread) & all_threads_mask); else { err |= ERR_FATAL | ERR_ALERT; msg = "UDP is not yet supported on this proxy mode"; goto udp_close_return; } udp_return: if (msg && errlen) { char pn[INET6_ADDRSTRLEN]; addr_to_str(&addr_inet, pn, sizeof(pn)); snprintf(errmsg, errlen, "%s [%s:%d]", msg, pn, get_host_port(&addr_inet)); } return err; udp_close_return: close(fd); goto udp_return; } /* Add to the list of udp4 listeners, on port . The * listener's state is automatically updated from LI_INIT to LI_ASSIGNED. * The number of listeners for the protocol is updated. */ static void udp4_add_listener(struct listener *listener, int port) { if (listener->state != LI_INIT) return; listener->state = LI_ASSIGNED; listener->rx.proto = &proto_udp4; ((struct sockaddr_in *)(&listener->rx.addr))->sin_port = htons(port); LIST_ADDQ(&proto_udp4.listeners, &listener->rx.proto_list); proto_udp4.nb_listeners++; } /* Add to the list of udp6 listeners, on port . The * listener's state is automatically updated from LI_INIT to LI_ASSIGNED. * The number of listeners for the protocol is updated. */ static void udp6_add_listener(struct listener *listener, int port) { if (listener->state != LI_INIT) return; listener->state = LI_ASSIGNED; listener->rx.proto = &proto_udp6; ((struct sockaddr_in *)(&listener->rx.addr))->sin_port = htons(port); LIST_ADDQ(&proto_udp6.listeners, &listener->rx.proto_list); proto_udp6.nb_listeners++; } /* Pause a listener. Returns < 0 in case of failure, 0 if the listener * was totally stopped, or > 0 if correctly paused. */ int udp_pause_listener(struct listener *l) { /* we don't support pausing on UDP */ return -1; } /* * Local variables: * c-indent-level: 8 * c-basic-offset: 8 * End: */