From patchwork Tue Jun 14 14:25:38 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Otto Hollmann X-Patchwork-Id: 12881190 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 2DED7C43334 for ; Tue, 14 Jun 2022 14:25:44 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S237995AbiFNOZm (ORCPT ); Tue, 14 Jun 2022 10:25:42 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:43794 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S236169AbiFNOZl (ORCPT ); Tue, 14 Jun 2022 10:25:41 -0400 Received: from smtp-out2.suse.de (smtp-out2.suse.de [195.135.220.29]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 4E2B52CE27 for ; Tue, 14 Jun 2022 07:25:40 -0700 (PDT) Received: from imap2.suse-dmz.suse.de (imap2.suse-dmz.suse.de [192.168.254.74]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-521) server-digest SHA512) (No client certificate requested) by smtp-out2.suse.de (Postfix) with ESMTPS id 076861F8F6; Tue, 14 Jun 2022 14:25:39 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.cz; s=susede2_rsa; t=1655216739; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc: mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=bDd90XKiMgODuOQ3PSH0JGyFEVry39DF/53FITgmXG8=; b=u9j6uBzHXc2J88c1KW61Tg3zEyzXTrnP5EBaZR/1YpX6v3f8JfizjaxIecX+ji9W0J2z+a x95UZ8+4NGx5H5nW+GTvy9cl7UkPRWizQlZ9GtJJCVLE+WXfqiRfj7rHXNntHzHkNWaZsm eU6DNP0qnhMsUH6R8rFWGa+Ds6J7kBE= DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=suse.cz; s=susede2_ed25519; t=1655216739; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc: mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=bDd90XKiMgODuOQ3PSH0JGyFEVry39DF/53FITgmXG8=; b=qNh9XhTZsRSFHHKAJQmJu0xJbz+mQEgJ9itLYtVLwr/yhkGvY2l3fxQly57EFmK3m2q6mE VTb2PMn6q2I9LTBA== Received: from imap2.suse-dmz.suse.de (imap2.suse-dmz.suse.de [192.168.254.74]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-521) server-digest SHA512) (No client certificate requested) by imap2.suse-dmz.suse.de (Postfix) with ESMTPS id DF16A139EC; Tue, 14 Jun 2022 14:25:38 +0000 (UTC) Received: from dovecot-director2.suse.de ([192.168.254.65]) by imap2.suse-dmz.suse.de with ESMTPSA id XP1yNWKaqGKEQwAAMHmgww (envelope-from ); Tue, 14 Jun 2022 14:25:38 +0000 Message-ID: Subject: [PATCH] binddynport.c honor ip_local_reserved_ports From: Otto Hollmann To: libtirpc-devel@lists.sourceforge.net Cc: linux-nfs@vger.kernel.org Date: Tue, 14 Jun 2022 16:25:38 +0200 Organization: SUSE User-Agent: Evolution 3.34.4 MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-nfs@vger.kernel.org Read reserved ports from /proc/sys/net/ipv4/ip_local_reserved_ports, store them into bit-wise array and before binding to random port check if port is not reserved. Currently, there is no way how to reserve ports so then will not be used by rpcbind. Random ports are opened by rpcbind because of rmtcalls. There is compile-time flag for disabling them, but in some cases we can not simply disable them. One solution would be run time option --enable-rmtcalls as already discussed, but it was rejected. So if we want to keep rmtcalls enabled and also be able to reserve some ports, there is no other way than filtering available ports. The easiest and clearest way seems to be just respect kernel list of ip_reserved_ports. Unfortunately there is one known disadvantage/side effect - it affects probability of ports which are right after reserved ones. The bigger reserved block is, the higher is probability of selecting following unreserved port. But if there is no reserved port, impact of this patch is minimal/none. Signed-off-by: Otto Hollmann --- src/binddynport.c | 107 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 99 insertions(+), 8 deletions(-) diff --git a/src/binddynport.c b/src/binddynport.c index 062629a..6f78ebe 100644 --- a/src/binddynport.c +++ b/src/binddynport.c @@ -37,6 +37,7 @@ #include #include #include +#include #include @@ -56,6 +57,84 @@ enum { NPORTS = ENDPORT - LOWPORT + 1, }; +/* + * This function decodes information about given port from provided array and + * return if port is reserved or not. + * + * @reserved_ports an array of size at least "NPORTS / (8*sizeof(char)) + 1". + * @port port number within range LOWPORT and ENDPORT + * + * Returns 0 if port is not reserved, non-negative if port is reserved. + */ +int is_reserved(char *reserved_ports, int port) { + port -= LOWPORT; + if (port < 0 || port >= NPORTS) + return 0; + return reserved_ports[port/(8*sizeof(char))] & 1<<(port%(8*sizeof(char))); +} + +/* + * This function encodes information about given *reserved* port into provided + * array. Don't call this function for ports which are not reserved. + * + * @reserved_ports an array of size at least "NPORTS / (8*sizeof(char)) + 1". + * @port port number within range LOWPORT and ENDPORT + * + */ +void set_reserved(char *reserved_ports, int port) { + port -= LOWPORT; + if (port < 0 || port >= NPORTS) + return; + reserved_ports[port/(8*sizeof(char))] |= 1<<(port%(8*sizeof(char))); +} + +/* + * Parse local reserved ports obtained from + * /proc/sys/net/ipv4/ip_local_reserved_ports into bit array. + * + * @reserved_ports a zeroed array of size at least + * "NPORTS / (8*sizeof(char)) + 1". Will be used for bit-wise encoding of + * reserved ports. + * + * On each call, reserved ports are read from /proc and bit-wise stored into + * provided array + * + * Returns 0 on success, -1 on failure. + */ + +int parse_reserved_ports(char *reserved_ports) { + int from, to; + char delimiter = ','; + int res; + FILE * file_ptr = fopen("/proc/sys/net/ipv4/ip_local_reserved_ports","r"); + if (file_ptr == NULL) { + (void) syslog(LOG_ERR, + "Unable to open open /proc/sys/net/ipv4/ip_local_reserved_ports."); + return -1; + } + do { + if ((res = fscanf(file_ptr, "%d", &to)) != 1) { + if (res == EOF) break; + goto err; + } + if (delimiter != '-') { + from = to; + } + for (int i = from; i <= to; ++i) { + set_reserved(reserved_ports, i); + } + } while ((res = fscanf(file_ptr, "%c", &delimiter)) == 1); + if (res != EOF) + goto err; + fclose(file_ptr); + return 0; +err: + (void) syslog(LOG_ERR, + "An error occurred while parsing ip_local_reserved_ports."); + fclose(file_ptr); + return -1; +} + /* * Bind a socket to a dynamically-assigned IP port. * @@ -81,7 +160,8 @@ int __binddynport(int fd) in_port_t port, *portp; struct sockaddr *sap; socklen_t salen; - int i, res; + int i, res, array_size; + char *reserved_ports; if (__rpc_sockisbound(fd)) return 0; @@ -119,21 +199,32 @@ int __binddynport(int fd) gettimeofday(&tv, NULL); seed = tv.tv_usec * getpid(); } + array_size = NPORTS / (8*sizeof(char)) + 1; + reserved_ports = malloc(array_size); + if (!reserved_ports) { + goto out; + } + memset(reserved_ports, 0, array_size); + parse_reserved_ports(reserved_ports); + port = (rand_r(&seed) % NPORTS) + LOWPORT; for (i = 0; i < NPORTS; ++i) { - *portp = htons(port++); - res = bind(fd, sap, salen); - if (res >= 0) { - res = 0; - break; + *portp = htons(port); + if (!is_reserved(reserved_ports, port++)) { + res = bind(fd, sap, salen); + if (res >= 0) { + res = 0; + break; + } + if (errno != EADDRINUSE) + break; } - if (errno != EADDRINUSE) - break; if (port > ENDPORT) port = LOWPORT; } out: + free(reserved_ports); mutex_unlock(&port_lock); return res; }