commit e6baa96f0bf07e0d86ace943472ffacfb3b32551 Author: Miroslav Lichvar (via linuxptp-devel Mailing List) Date: Thu Jul 31 11:35:44 2025 +0200 util: Add functions for dropping root privileges. Add a function to switch the process UID/GID to a specified user in order to drop the root privileges. Keep only the capabilities needed to adjust the clock, enable HW timestamping, bind to privileged ports and raw sockets, using the libcap library. Initialize the supplementary groups of the user to make it easier to give it permissions to open PHC devices by adding it to a system group. Add a function to create a directory for a UDS address with a specified owner, where the programs will be able to bind and unlink their sockets without root privileges. (Rebased to 4.4) Signed-off-by: Miroslav Lichvar Reviewed-by: Jacob Keller diff --git a/incdefs.sh b/incdefs.sh index 060cc0b..4af992d 100755 --- a/incdefs.sh +++ b/incdefs.sh @@ -140,6 +140,14 @@ user_flags() fi done done + + # Look for libcap support. + for d in $dirs; do + if test -e $d/sys/capability.h; then + printf " -DHAVE_LIBCAP" + break + fi + done } # diff --git a/makefile b/makefile index 3c2406b..2069e7e 100644 --- a/makefile +++ b/makefile @@ -62,6 +62,10 @@ LDLIBS += -lcrypto SECURITY += sad_openssl.o endif +ifneq (,$(findstring -DHAVE_LIBCAP,$(incdefs))) +LDLIBS += -lcap +endif + prefix = /usr/local sbindir = $(prefix)/sbin mandir = $(prefix)/man diff --git a/util.c b/util.c index 5b0302b..c211158 100644 --- a/util.c +++ b/util.c @@ -19,13 +19,25 @@ #include #include #include +#include #include #include #include #include #include +#include +#include + +#ifdef HAVE_LIBCAP +#include +#include +#include +#include +#include +#endif #include "address.h" +#include "interface.h" #include "phc.h" #include "print.h" #include "sk.h" @@ -894,3 +906,102 @@ bool base64_decode(const char *in_str, size_t in_len, void *out, size_t *out_len *out_len = *out_len - len; return true; } + +void create_uds_directory(const char *address, const char *user) +{ + char path[MAX_IFNAME_SIZE + 1], *dir; +#ifdef HAVE_LIBCAP + struct passwd *pw; +#endif + + if (snprintf(path, sizeof(path), "%s", address) >= sizeof(path)) { + pr_err("path too long for UDS"); + return; + } + + dir = dirname(path); + + /* Don't do anything if it already exists or cannot be created */ + if (mkdir(dir, 0775)) { + if (errno != EEXIST) + pr_err("failed to create %s: %m", dir); + return; + } + +#ifdef HAVE_LIBCAP + if (user[0] == '\0') + return; + + pw = getpwnam(user); + if (!pw) { + pr_err("failed to get user/group ID of %s", user); + rmdir(dir); + return; + } + + if (lchown(dir, pw->pw_uid, pw->pw_gid)) { + pr_err("failed to change owner of %s: %m", dir); + rmdir(dir); + return; + } +#endif +} + +int drop_root_privileges(const char *user) +{ +#ifdef HAVE_LIBCAP + struct passwd *pw; + cap_t cap; +#endif + + if (user[0] == '\0') + return 0; + +#ifdef HAVE_LIBCAP + pw = getpwnam(user); + if (!pw) { + pr_err("failed to get user/group ID of %s", user); + return -1; + } + + if (prctl(PR_SET_KEEPCAPS, 1)) { + pr_err("failed to set KEEPCAPS flag"); + return -1; + } + + if (initgroups(user, pw->pw_gid)) { + pr_err("failed to init supplementary groups"); + return -1; + } + + if (setgid(pw->pw_gid)) { + pr_err("failed to set group ID"); + return -1; + } + + if (setuid(pw->pw_uid)) { + pr_err("failed to set user ID"); + return -1; + } + + cap = cap_from_text("cap_sys_time,cap_net_admin," + "cap_net_bind_service,cap_net_raw=ep"); + if (!cap) { + pr_err("failed to initialize capabilities"); + return -1; + } + + if (cap_set_proc(cap)) { + pr_err("failed to set process capabilities"); + cap_free(cap); + return -1; + } + + cap_free(cap); + + return 0; +#else + pr_err("cannot drop root privileges without libcap"); + return -1; +#endif +} diff --git a/util.h b/util.h index b228745..ed48b21 100644 --- a/util.h +++ b/util.h @@ -502,4 +502,24 @@ size_t base64_len(const char *str, size_t len); */ bool base64_decode(const char *in_str, size_t in_len, void *out, size_t *out_len); +/* + * Create directory for a UDS address owned by the specified user. + * Don't do anything if it already exists, even if it has a different owner. + * + * @param address UDS address. + * @param user Name of the user. + */ +void create_uds_directory(const char *address, const char *user); + +/** + * Change the user/group ID in order to drop the root privileges. Initialize + * supplementary groups of the user. Keep only capabilities needed to set + * and adjust clocks, bind to a privileged port, enable and configure HW + * timestamping, and open a raw socket. + * + * @param user Name of the user. + * @return 0 on success, -1 on error. + */ +int drop_root_privileges(const char *user); + #endif commit ee15ee7cfadd013cd7ccc160d6d505d674275740 Author: Miroslav Lichvar (via linuxptp-devel Mailing List) Date: Thu Jul 31 11:35:45 2025 +0200 clock: Add support for dropping root privileges. Add "user" option to specify the user to which ptp4l should switch after opening the PHC device and before binding the network/UDS ports. Create the directory that will contain the UDS-RW/RO sockets if it does not exist and change its ownership to allow the UDS sockets to be bound and unlinked there. The default is to keep the identity of the user which started ptp4l. In the jbod mode, or with a bond interface, the PHC devices need to have the permissions set for the non-root user to be able to open them (e.g. by adding the user to a system group owning the PHC devices). Signed-off-by: Miroslav Lichvar Reviewed-by: Jacob Keller diff --git a/clock.c b/clock.c index 7c30066..7ffdbaa 100644 --- a/clock.c +++ b/clock.c @@ -1109,7 +1109,7 @@ struct clock *clock_create(enum clock_type type, struct config *config, { int conf_phc_index, i, max_adj = 0, phc_index, required_modes = 0, sfl, sw_ts; enum servo_type servo = config_get_int(config, NULL, "clock_servo"); - char ts_label[IF_NAMESIZE], phc[32], *tmp; + char ts_label[IF_NAMESIZE], phc[32], *tmp, *user; enum timestamp_type timestamping; struct clock *c = &the_clock; const char *uds_ifname; @@ -1429,6 +1429,12 @@ struct clock *clock_create(enum clock_type type, struct config *config, return NULL; } + user = config_get_string(config, NULL, "user"); + create_uds_directory(config_get_string(config, NULL, "uds_address"), + user); + create_uds_directory(config_get_string(config, NULL, "uds_ro_address"), + user); + /* Create the UDS interfaces. */ c->uds_rw_port = port_open(phc_device, phc_index, timestamping, 0, @@ -1459,6 +1465,18 @@ struct clock *clock_create(enum clock_type type, struct config *config, } } + /* + * Drop the root privileges if configured to do so. It needs to be done + * after opening the ports as generic netlink sockets (whose binding + * requires CAP_SYS_ADMIN) are used to detect virtual clocks. The port + * sockets will be bound and HW timestamping configured later when + * handling the port INITIALIZING state. The process keeps + * CAP_NET_ADMIN, CAP_NET_BIND_SERVICE, and CAP_NET_RAW to be able to + * do that. + */ + if (drop_root_privileges(user)) + return NULL; + c->dds.numberPorts = c->nports; LIST_FOREACH(p, &c->ports, list) { diff --git a/config.c b/config.c index 6c602ef..3c5d143 100644 --- a/config.c +++ b/config.c @@ -395,6 +395,7 @@ struct config_item config_tab[] = { PORT_ITEM_INT("unicast_master_table", 0, 0, INT_MAX), PORT_ITEM_INT("unicast_req_duration", 3600, 10, INT_MAX), GLOB_ITEM_INT("use_syslog", 1, 0, 1), + GLOB_ITEM_STR("user", ""), GLOB_ITEM_STR("userDescription", ""), GLOB_ITEM_INT("utc_offset", CURRENT_UTC_OFFSET, 0, INT_MAX), GLOB_ITEM_INT("verbose", 0, 0, 1), diff --git a/ptp4l.8 b/ptp4l.8 index 87900e3..a302997 100644 --- a/ptp4l.8 +++ b/ptp4l.8 @@ -1040,7 +1040,10 @@ is only relevant with IPv6 transport. See RFC 4291. The default is .TP .B uds_address Specifies the address of the UNIX domain socket for receiving local -management messages. The default is /var/run/ptp4l. +management messages. The directory containing the socket will be created on +start if it does not exist, with owner set to the user specified by the +.B user +option. The default is /var/run/ptp4l. .TP .B uds_file_mode @@ -1088,6 +1091,19 @@ This option enables using the "write phase" feature of a PTP Hardware Clock. If supported by the device, this mode uses the hardware's built in phase offset control instead of frequency offset control. The default value is 0 (disabled). +.TP +.B user +The name of the user to which should +.B ptp4l +switch after start in order to drop the root privileges. By default, +.B ptp4l +will keep the identity of the user under which it is started. With the +.B boundary_clock_jbod +option, or if using a bonded interface, the non-root user must have permissions +to open the PHC devices to be able to switch between them. If using the ntpshm +or refclock_sock servo, the user must have also permissions to attach to the +shared memory segment if it already exists or connect to the refclock socket +respectively. .SH UNICAST DISCOVERY OPTIONS commit 91fb662377222a4ef58fce550a8488ef2768d57e Author: Miroslav Lichvar (via linuxptp-devel Mailing List) Date: Thu Jul 31 11:35:46 2025 +0200 uds: Copy ownership of server socket in pmc clients. ptp4l sending a response to a pmc client needs to have permissions to write to the client's UNIX domain socket. If ptp4l runs under a non-root user, it cannot write to sockets bound by the pmc client if it did that as root. After binding the client socket, change its owner to the owner of the server socket, so it can send the client a response. Signed-off-by: Miroslav Lichvar Reviewed-by: Jacob Keller diff --git a/uds.c b/uds.c index 4ddee7b..ce7e92d 100644 --- a/uds.c +++ b/uds.c @@ -60,6 +60,7 @@ static int uds_open(struct transport *t, struct interface *iface, struct fdarray const char* file_mode_cfg; struct sockaddr_un sa; mode_t file_mode; + struct stat st; int fd, err; fd = socket(AF_LOCAL, SOCK_DGRAM, 0); @@ -97,6 +98,20 @@ static int uds_open(struct transport *t, struct interface *iface, struct fdarray uds->address.len = sizeof(sa); chmod(name, file_mode); + + /* + * In the client, copy the ownership of the server's socket if it runs + * under a non-root user to allow it to send a response to the client + * running under root. Avoid following a symlink if the socket is + * replaced (e.g. by compromised ptp4l process). + */ + if (uds_path[0] != '\0') { + if (!lstat(uds_path, &st) && (st.st_uid || st.st_gid) && + lchown(name, st.st_uid, st.st_gid)) { + pr_err("uds: failed to change socket ownership: %m"); + } + } + fda->fd[FD_EVENT] = -1; fda->fd[FD_GENERAL] = fd; return 0; commit eb95a385c9e5653b55b25166f65ca0d8139a2c6e Author: Miroslav Lichvar (via linuxptp-devel Mailing List) Date: Thu Jul 31 11:35:47 2025 +0200 uds: Don't call chmod() on client socket. The pmc clients should not need to modify the permissions of their socket (following the uds_file_mode setting), they can rely on their umask. Make the chmod() call on the bound socket only in the server. This removes a race condition between the bind() and chmod() calls that could potentially be exploited by ptp4l running under a non-root user. It could replace the socket with a symlink in order to make the client running under root to change the mode of a different file. Signed-off-by: Miroslav Lichvar Reviewed-by: Jacob Keller diff --git a/uds.c b/uds.c index ce7e92d..d74b7a8 100644 --- a/uds.c +++ b/uds.c @@ -97,19 +97,20 @@ static int uds_open(struct transport *t, struct interface *iface, struct fdarray uds->address.sun = sa; uds->address.len = sizeof(sa); - chmod(name, file_mode); - /* * In the client, copy the ownership of the server's socket if it runs * under a non-root user to allow it to send a response to the client * running under root. Avoid following a symlink if the socket is - * replaced (e.g. by compromised ptp4l process). + * replaced (e.g. by compromised ptp4l process). The server just sets + * the permissions on its socket per configuration. */ if (uds_path[0] != '\0') { if (!lstat(uds_path, &st) && (st.st_uid || st.st_gid) && lchown(name, st.st_uid, st.st_gid)) { pr_err("uds: failed to change socket ownership: %m"); } + } else { + chmod(name, file_mode); } fda->fd[FD_EVENT] = -1; commit 48367c57de49671ae36c1df7d4d2c344ac164ef6 Author: Miroslav Lichvar (via linuxptp-devel Mailing List) Date: Thu Jul 31 11:35:48 2025 +0200 Follow server UDS location in pmc clients. Modify pmc, phc2sys, ts2phc, and tz2alt to bind their client sockets in the same directory as the server's socket instead of hardcoded /var/run. In the pmc program it's the default, it can still be configured with the -i option to any address. This allows the clients to be started under the same user as to which ptp4l is configured to switch, with all their sockets in the same directory where the user has write permissions. phc2sys and ts2phc will need also permissions to open the PHC devices and the CAP_SYS_TIME capability needed to adjust the clock (e.g. set by setpriv(1)). Signed-off-by: Miroslav Lichvar Reviewed-by: Jacob Keller diff --git a/phc2sys.c b/phc2sys.c index ddd0a6a..dd72c17 100644 --- a/phc2sys.c +++ b/phc2sys.c @@ -1512,7 +1512,7 @@ int main(int argc, char *argv[]) } snprintf(uds_local, sizeof(uds_local), - "/var/run/phc2sys.%d.%d", getpid(), i); + "phc2sys.%d.%d", getpid(), i); if (uds_remote_cnt > i) config_set_string(cfg, "uds_address", @@ -1554,8 +1554,7 @@ int main(int argc, char *argv[]) r = -1; if (wait_sync || !domains[0].forced_sync_offset) { - snprintf(uds_local, sizeof(uds_local), - "/var/run/phc2sys.%d", getpid()); + snprintf(uds_local, sizeof(uds_local), "phc2sys.%d", getpid()); if (uds_remote_cnt > 0) config_set_string(cfg, "uds_address", diff --git a/pmc.8 b/pmc.8 index 2b08f4d..1019d59 100644 --- a/pmc.8 +++ b/pmc.8 @@ -81,8 +81,9 @@ Specify the boundary hops value in sent messages. The default is 1. Specify the domain number in sent messages. The default is 0. .TP .BI \-i " interface" -Specify the network interface. The default is /var/run/pmc.$pid for the Unix Domain -Socket transport and eth0 for the other transports. +Specify the network interface. The default is pmc.$pid for the Unix Domain +Socket transport and eth0 for the other transports. A relative path of the Unix +Domain socket is relative to the directory of the server's socket. .TP .BI \-s " uds-address" Specifies the address of the server's UNIX domain socket. diff --git a/pmc.c b/pmc.c index 6a6a94e..c75879f 100644 --- a/pmc.c +++ b/pmc.c @@ -705,7 +705,7 @@ static void usage(char *progname) " -f [file] read configuration from 'file'\n" " -h prints this message and exits\n" " -i [dev] interface device to use, default 'eth0'\n" - " for network and '/var/run/pmc.$pid' for UDS.\n" + " for network and 'pmc.$pid' for UDS.\n" " -s [path] server address for UDS, default '/var/run/ptp4l'.\n" " -t [hex] transport specific field, default 0x0\n" " -v prints the software version and exits\n" @@ -845,7 +845,7 @@ int main(int argc, char *argv[]) if (!iface_name) { if (transport_type == TRANS_UDS) { snprintf(uds_local, sizeof(uds_local), - "/var/run/pmc.%d", getpid()); + "pmc.%d", getpid()); iface_name = uds_local; } else { iface_name = "eth0"; diff --git a/pmc_common.c b/pmc_common.c index b55e7ec..a67b01f 100644 --- a/pmc_common.c +++ b/pmc_common.c @@ -18,6 +18,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include +#include #include #include #include @@ -515,12 +516,38 @@ struct pmc { UInteger8 allow_unauth; }; +static int complete_uds_address(struct config *cfg, const char *iface, + char *uds, size_t len) +{ + char buf[MAX_IFNAME_SIZE + 1]; + + /* Don't change absolute paths */ + if (iface[0] == '/') { + if (snprintf(uds, len, "%s", iface) >= len) { + pr_err("UDS path too long"); + return -1; + } + return 0; + } + + /* Relative paths are relative to the directory of the server socket */ + if (snprintf(buf, sizeof(buf), "%s", config_get_string(cfg, NULL, + "uds_address")) >= sizeof(buf) || + snprintf(uds, len, "%s/%s", dirname(buf), iface) >= len) { + pr_err("UDS path too long"); + return -1; + } + + return 0; +} + struct pmc *pmc_create(struct config *cfg, enum transport_type transport_type, const char *iface_name, const char *remote_address, UInteger8 boundary_hops, UInteger8 domain_number, UInteger8 transport_specific, UInteger8 allow_unauth, int zero_datalen) { + char uds[MAX_IFNAME_SIZE + 1]; struct pmc *pmc; UInteger32 proc_id; @@ -533,6 +560,9 @@ struct pmc *pmc_create(struct config *cfg, enum transport_type transport_type, pmc->port_identity.clockIdentity.id[6] = (proc_id & 0xFF000000) >> 24; pmc->port_identity.clockIdentity.id[7] = (proc_id & 0x00FF0000) >> 16; pmc->port_identity.portNumber = proc_id & 0xFFFF; + if (complete_uds_address(cfg, iface_name, uds, sizeof(uds))) + goto failed; + iface_name = uds; } else { if (generate_clock_identity(&pmc->port_identity.clockIdentity, iface_name)) { diff --git a/ts2phc.c b/ts2phc.c index 5448496..2d699c6 100644 --- a/ts2phc.c +++ b/ts2phc.c @@ -715,8 +715,7 @@ int main(int argc, char *argv[]) return -1; } - snprintf(uds_local, sizeof(uds_local), "/var/run/ts2phc.%d", - getpid()); + snprintf(uds_local, sizeof(uds_local), "ts2phc.%d", getpid()); if (autocfg) { err = init_pmc_node(cfg, priv.agent, uds_local, diff --git a/tz2alt.c b/tz2alt.c index 905aa0b..afd1941 100644 --- a/tz2alt.c +++ b/tz2alt.c @@ -384,7 +384,7 @@ int main(int argc, char *argv[]) print_set_progname(progname); print_set_tag(config_get_string(cfg, NULL, "message_tag")); print_set_level(config_get_int(cfg, NULL, "logging_level")); - snprintf(uds_local, sizeof(uds_local), "/var/run/tztool.%d", getpid()); + snprintf(uds_local, sizeof(uds_local), "tztool.%d", getpid()); err = do_tztool(timezone); out: commit 009a794e098e0de4db03d3bae33216a9aecce6bb Author: Miroslav Lichvar (via linuxptp-devel Mailing List) Date: Thu Jul 31 11:35:49 2025 +0200 config: Move default UDS addresses to /var/run/ptp. /var/run is normally not writable for non-root users. Move the default server UDS and CMLDS addresses to /var/run/ptp, which will be created by ptp4l the first time it runs if it does not exist. This makes the default work well in both cases when the linuxptp programs are running under root and non-root user. Signed-off-by: Miroslav Lichvar Reviewed-by: Jacob Keller diff --git a/config.c b/config.c index 3c5d143..c05abcd 100644 --- a/config.c +++ b/config.c @@ -387,9 +387,9 @@ struct config_item config_tab[] = { GLOB_ITEM_INT("tx_timestamp_timeout", 10, 1, INT_MAX), PORT_ITEM_INT("udp_ttl", 1, 1, 255), PORT_ITEM_INT("udp6_scope", 0x0E, 0x00, 0x0F), - GLOB_ITEM_STR("uds_address", "/var/run/ptp4l"), + GLOB_ITEM_STR("uds_address", "/var/run/ptp/ptp4l"), PORT_ITEM_INT("uds_file_mode", UDS_FILEMODE, 0, 0777), - GLOB_ITEM_STR("uds_ro_address", "/var/run/ptp4lro"), + GLOB_ITEM_STR("uds_ro_address", "/var/run/ptp/ptp4lro"), PORT_ITEM_INT("uds_ro_file_mode", UDS_RO_FILEMODE, 0, 0777), PORT_ITEM_INT("unicast_listen", 0, 0, 1), PORT_ITEM_INT("unicast_master_table", 0, 0, INT_MAX), diff --git a/configs/CMLDS_server.cfg b/configs/CMLDS_server.cfg index 36f118c..842fbe4 100644 --- a/configs/CMLDS_server.cfg +++ b/configs/CMLDS_server.cfg @@ -10,7 +10,7 @@ clockIdentity C37D50.0000.000000 free_running 1 ignore_transport_specific 1 transportSpecific 2 -uds_address /var/run/cmlds_server +uds_address /var/run/ptp/cmlds_server [eth1] delay_mechanism P2P diff --git a/configs/default.cfg b/configs/default.cfg index c3ad618..c2e0f93 100644 --- a/configs/default.cfg +++ b/configs/default.cfg @@ -96,11 +96,11 @@ write_phase_mode 0 # # Transport options # -cmlds.client_address /var/run/cmlds_client +cmlds.client_address /var/run/ptp/cmlds_client cmlds.domainNumber 0 cmlds.majorSdoId 2 cmlds.port 0 -cmlds.server_address /var/run/cmlds_server +cmlds.server_address /var/run/ptp/cmlds_server transportSpecific 0x0 ptp_dst_ipv4 224.0.1.129 p2p_dst_ipv4 224.0.0.107 @@ -110,9 +110,9 @@ ptp_dst_mac 01:1B:19:00:00:00 p2p_dst_mac 01:80:C2:00:00:0E udp_ttl 1 udp6_scope 0x0E -uds_address /var/run/ptp4l +uds_address /var/run/ptp/ptp4l uds_file_mode 0660 -uds_ro_address /var/run/ptp4lro +uds_ro_address /var/run/ptp/ptp4lro uds_ro_file_mode 0666 # # Default interface options diff --git a/phc2sys.8 b/phc2sys.8 index 3246c8f..80d009f 100644 --- a/phc2sys.8 +++ b/phc2sys.8 @@ -224,7 +224,7 @@ Specifies the address of the server's UNIX domain socket. This option can be used up to 16 times in the automatic mode to synchronize clocks between multiple .B ptp4l instances. -The default is /var/run/ptp4l. +The default is /var/run/ptp/ptp4l. .TP .BI \-l " print-level" Set the maximum syslog level of messages which should be printed or sent to @@ -420,7 +420,7 @@ The default is 0. .TP .B uds_address Specifies the address of the server's UNIX domain socket. The default -is /var/run/ptp4 +is /var/run/ptp/ptp4l Same as option .B \-z (see above). diff --git a/phc2sys.c b/phc2sys.c index dd72c17..1269d18 100644 --- a/phc2sys.c +++ b/phc2sys.c @@ -1202,7 +1202,7 @@ static void usage(char *progname) " -u [num] number of clock updates in summary stats (0)\n" " -n [num] domain number (0)\n" " -x apply leap seconds by servo instead of kernel\n" - " -z [path] server address for UDS (/var/run/ptp4l)\n" + " -z [path] server address for UDS (/var/run/ptp/ptp4l)\n" " -l [num] set the logging level to 'num' (6)\n" " -t [tag] add tag to log messages\n" " -m print messages to stdout\n" diff --git a/pmc.8 b/pmc.8 index 1019d59..4fb740f 100644 --- a/pmc.8 +++ b/pmc.8 @@ -87,7 +87,7 @@ Domain socket is relative to the directory of the server's socket. .TP .BI \-s " uds-address" Specifies the address of the server's UNIX domain socket. -The default is /var/run/ptp4l. +The default is /var/run/ptp/ptp4l. .TP .BI \-t " transport-specific-field" Specify the transport specific field in sent messages as a hexadecimal number. diff --git a/pmc.c b/pmc.c index c75879f..df6bdec 100644 --- a/pmc.c +++ b/pmc.c @@ -706,7 +706,7 @@ static void usage(char *progname) " -h prints this message and exits\n" " -i [dev] interface device to use, default 'eth0'\n" " for network and 'pmc.$pid' for UDS.\n" - " -s [path] server address for UDS, default '/var/run/ptp4l'.\n" + " -s [path] server address for UDS, default '/var/run/ptp/ptp4l'.\n" " -t [hex] transport specific field, default 0x0\n" " -v prints the software version and exits\n" " -z send zero length TLV values with the GET actions\n" diff --git a/ptp4l.8 b/ptp4l.8 index a302997..d66843f 100644 --- a/ptp4l.8 +++ b/ptp4l.8 @@ -1043,7 +1043,7 @@ Specifies the address of the UNIX domain socket for receiving local management messages. The directory containing the socket will be created on start if it does not exist, with owner set to the user specified by the .B user -option. The default is /var/run/ptp4l. +option. The default is /var/run/ptp/ptp4l. .TP .B uds_file_mode @@ -1056,7 +1056,7 @@ should start with a 0 literal. The default mode is 0660. Specifies the address of the second UNIX domain socket for receiving local management messages, which is restricted to GET actions and does not forward messages to other ports. Access to this socket can be given to untrusted -applications for monitoring purposes. The default is /var/run/ptp4lro. +applications for monitoring purposes. The default is /var/run/ptp/ptp4lro. .TP .B uds_ro_file_mode commit b2e854f1ea5d97360142cf8423fe9c08cd6d5747 Author: Miroslav Lichvar (via linuxptp-devel Mailing List) Date: Thu Jul 31 11:35:50 2025 +0200 clock: Create compatibility symlinks for UDS. If using the new default UDS-RW/RO addresses, create symlinks at the original location for compatibility with non-linuxptp clients and scripts. This works only when ptp4l is not configured to drop root privileges, otherwise it will not be able to respond to a socket owned by root. Don't call unlink() to remove an existing socket before trying to create the symlink. It might be a different ptp4l instance still running. Print a warning message if the path is not already a symlink and the symlink cannot be created. Suggested-by: Martin Pecka Signed-off-by: Miroslav Lichvar Reviewed-by: Jacob Keller diff --git a/clock.c b/clock.c index 7ffdbaa..17484d8 100644 --- a/clock.c +++ b/clock.c @@ -24,6 +24,7 @@ #include #include #include +#include #include "address.h" #include "bmc.h" @@ -1104,6 +1105,16 @@ int clock_required_modes(struct clock *c) return required_modes; } +static void create_symlink(const char *target, const char *path) +{ + struct stat st; + + if (!lstat(path, &st) && (st.st_mode & S_IFMT) == S_IFLNK) + return; + if (symlink(target, path)) + pr_warning("failed to create symlink %s->%s: %m", path, target); +} + struct clock *clock_create(enum clock_type type, struct config *config, const char *phc_device) { @@ -1268,9 +1279,14 @@ struct clock *clock_create(enum clock_type type, struct config *config, } } - /* Configure the UDS. */ + /* + * Configure the UDS ports. If using the new default server addresses, + * create also symlinks for compatibility with older clients. + */ uds_ifname = config_get_string(config, NULL, "uds_address"); + if (!strcmp(uds_ifname, "/var/run/ptp/ptp4l")) + create_symlink("ptp/ptp4l", "/var/run/ptp4l"); c->uds_rw_if = interface_create(uds_ifname, NULL); if (config_set_section_int(config, interface_name(c->uds_rw_if), "announceReceiptTimeout", 0)) { @@ -1291,6 +1307,8 @@ struct clock *clock_create(enum clock_type type, struct config *config, uds_ifname = config_get_string(config, NULL, "uds_ro_address"); c->uds_ro_if = interface_create(uds_ifname, NULL); + if (!strcmp(uds_ifname, "/var/run/ptp/ptp4lro")) + create_symlink("ptp/ptp4lro", "/var/run/ptp4lro"); if (config_set_section_int(config, interface_name(c->uds_ro_if), "announceReceiptTimeout", 0)) { return NULL; commit b5ea7e569e308f8bb69160f86c3e341572166c3d Author: Miroslav Lichvar Date: Thu Oct 2 13:09:32 2025 +0200 pmc_agent: Keep pmc_ds_requested set across run_pmc() calls. In the handling of timeout in run_pmc() the pmc_ds_requested variable is always cleared to force the next call of the function to send a new request if a response is not received in that time. This prevents detection of a missed response as nothing is tracking if a request was sent in the previous call. The variable is also set in run_pmc_wait_sync() to avoid sending a new request when waiting multiple port-specific responses. Modify the function to accept a new parameter enabling the receive-only mode and keep the pmc_ds_requested variable set on the timeout to make it clear when a response is missing, which will be needed in the next commit to reopen the transport. Reviewed-by: Jacob Keller Signed-off-by: Miroslav Lichvar diff --git a/pmc_agent.c b/pmc_agent.c index 663adc0..70d46cf 100644 --- a/pmc_agent.c +++ b/pmc_agent.c @@ -136,16 +136,16 @@ static int run_pmc_err2errno(int code) } static int run_pmc(struct pmc_agent *node, int timeout, int ds_id, - struct ptp_message **msg) + int recv_only, struct ptp_message **msg) { #define N_FD 1 struct pollfd pollfd[N_FD]; - int cnt, res; + int cnt, res, req_sent = 0; while (1) { pollfd[0].fd = pmc_get_transport_fd(node->pmc); pollfd[0].events = POLLIN|POLLPRI; - if (!node->pmc_ds_requested && ds_id >= 0) + if (!recv_only && !req_sent && ds_id >= 0) pollfd[0].events |= POLLOUT; cnt = poll(pollfd, N_FD, timeout); @@ -154,8 +154,7 @@ static int run_pmc(struct pmc_agent *node, int timeout, int ds_id, return RUN_PMC_INTR; } if (!cnt) { - /* Request the data set again in the next run. */ - node->pmc_ds_requested = 0; + pr_debug("poll timeout"); return RUN_PMC_TMO; } @@ -171,6 +170,7 @@ static int run_pmc(struct pmc_agent *node, int timeout, int ds_id, break; } node->pmc_ds_requested = 1; + req_sent = 1; } if (!(pollfd[0].revents & (POLLIN|POLLPRI))) @@ -209,7 +209,7 @@ static int renew_subscription(struct pmc_agent *node, int timeout) struct ptp_message *msg; int res; - res = run_pmc(node, timeout, MID_SUBSCRIBE_EVENTS_NP, &msg); + res = run_pmc(node, timeout, MID_SUBSCRIBE_EVENTS_NP, 0, &msg); if (is_run_pmc_error(res)) { return run_pmc_err2errno(res); } @@ -221,11 +221,11 @@ int run_pmc_wait_sync(struct pmc_agent *node, int timeout) { struct ptp_message *msg; Enumeration8 portState; + int res, recv_only = 0; void *data; - int res; while (1) { - res = run_pmc(node, timeout, MID_PORT_DATA_SET, &msg); + res = run_pmc(node, timeout, MID_PORT_DATA_SET, recv_only, &msg); if (res <= 0) return res; @@ -239,7 +239,7 @@ int run_pmc_wait_sync(struct pmc_agent *node, int timeout) return 1; } /* try to get more data sets (for other ports) */ - node->pmc_ds_requested = 1; + recv_only = 1; } } @@ -307,7 +307,7 @@ int pmc_agent_query_dds(struct pmc_agent *node, int timeout) struct defaultDS *dds; int res; - res = run_pmc(node, timeout, MID_DEFAULT_DATA_SET, &msg); + res = run_pmc(node, timeout, MID_DEFAULT_DATA_SET, 0, &msg); if (is_run_pmc_error(res)) { return run_pmc_err2errno(res); } @@ -325,11 +325,12 @@ int pmc_agent_query_port_properties(struct pmc_agent *node, int timeout, struct port_properties_np *ppn; struct port_hwclock_np *phn; struct ptp_message *msg; - int res, len; + int res, len, recv_only = 1; pmc_target_port(node->pmc, port); - while (1) { - res = run_pmc(node, timeout, MID_PORT_PROPERTIES_NP, &msg); + for (recv_only = 0; ; recv_only = 1) { + res = run_pmc(node, timeout, MID_PORT_PROPERTIES_NP, recv_only, + &msg); if (is_run_pmc_error(res)) { goto out; } @@ -350,8 +351,9 @@ int pmc_agent_query_port_properties(struct pmc_agent *node, int timeout, msg_put(msg); break; } - while (1) { - res = run_pmc(node, timeout, MID_PORT_HWCLOCK_NP, &msg); + for (recv_only = 0; ; recv_only = 1) { + res = run_pmc(node, timeout, MID_PORT_HWCLOCK_NP, recv_only, + &msg); if (is_run_pmc_error(res)) { goto out; } @@ -377,7 +379,7 @@ int pmc_agent_query_utc_offset(struct pmc_agent *node, int timeout) struct ptp_message *msg; int res; - res = run_pmc(node, timeout, MID_TIME_PROPERTIES_DATA_SET, &msg); + res = run_pmc(node, timeout, MID_TIME_PROPERTIES_DATA_SET, 0, &msg); if (is_run_pmc_error(res)) { return run_pmc_err2errno(res); } @@ -450,7 +452,7 @@ int pmc_agent_update(struct pmc_agent *node) break; } } else { - run_pmc(node, 0, -1, &msg); + run_pmc(node, 0, -1, 1, &msg); } return 0; commit fc0df58aec568121bde53781fb7d16f3dd0b9389 Author: Miroslav Lichvar Date: Thu Oct 2 13:57:53 2025 +0200 pmc_agent: Reopen transport after missed response. If the pmc agent (used in phc2sys and ts2phc) was started as root before ptp4l configured to not run as root could bind its socket, the client socket owner was not changed to follow the server socket, which caused the non-privileged server to not be able to send responses to the client. Add a new function to pmc_common to allow reopening the transport without destroying the pmc instance, and call it in the agent when a response from ptp4l is missed. This allows the client to correct the socket owner if it was started too soon in order to receive responses from the server. It also allows the client to recover if its socket is inadvertently removed. Reviewed-by: Jacob Keller Signed-off-by: Miroslav Lichvar diff --git a/pmc_agent.c b/pmc_agent.c index 70d46cf..51ef61e 100644 --- a/pmc_agent.c +++ b/pmc_agent.c @@ -144,6 +144,12 @@ static int run_pmc(struct pmc_agent *node, int timeout, int ds_id, while (1) { pollfd[0].fd = pmc_get_transport_fd(node->pmc); + if (pollfd[0].fd < 0) { + if (pmc_reopen_transport(node->pmc)) + return RUN_PMC_NODEV; + continue; + } + pollfd[0].events = POLLIN|POLLPRI; if (!recv_only && !req_sent && ds_id >= 0) pollfd[0].events |= POLLOUT; @@ -161,6 +167,12 @@ static int run_pmc(struct pmc_agent *node, int timeout, int ds_id, /* Send a new request if there are no pending messages. */ if ((pollfd[0].revents & POLLOUT) && !(pollfd[0].revents & (POLLIN|POLLPRI))) { + /* Reopen the transport if a response was missed. */ + if (node->pmc_ds_requested) { + if (pmc_reopen_transport(node->pmc)) + return RUN_PMC_NODEV; + } + switch (ds_id) { case MID_SUBSCRIBE_EVENTS_NP: send_subscription(node); diff --git a/pmc_common.c b/pmc_common.c index a67b01f..246aeb3 100644 --- a/pmc_common.c +++ b/pmc_common.c @@ -597,11 +597,10 @@ struct pmc *pmc_create(struct config *cfg, enum transport_type transport_type, goto failed; } - if (transport_open(pmc->transport, pmc->iface, - &pmc->fdarray, TS_SOFTWARE)) { - pr_err("failed to open transport"); + pmc->fdarray.fd[FD_GENERAL] = -1; + if (pmc_reopen_transport(pmc)) goto no_trans_open; - } + pmc->zero_length_gets = zero_datalen ? 1 : 0; return pmc; @@ -615,6 +614,23 @@ failed: return NULL; } +int pmc_reopen_transport(struct pmc *pmc) +{ + pr_debug("(re)opening transport"); + + if (pmc_get_transport_fd(pmc) >= 0) + transport_close(pmc->transport, &pmc->fdarray); + pmc->fdarray.fd[FD_GENERAL] = -1; + + if (transport_open(pmc->transport, pmc->iface, + &pmc->fdarray, TS_SOFTWARE)) { + pr_err("failed to open transport"); + return -1; + } + + return 0; +} + void pmc_destroy(struct pmc *pmc) { transport_close(pmc->transport, &pmc->fdarray); diff --git a/pmc_common.h b/pmc_common.h index 7b76061..93701fb 100644 --- a/pmc_common.h +++ b/pmc_common.h @@ -34,6 +34,8 @@ struct pmc *pmc_create(struct config *cfg, enum transport_type transport_type, UInteger8 transport_specific, UInteger8 allow_unauth, int zero_datalen); +int pmc_reopen_transport(struct pmc *pmc); + void pmc_destroy(struct pmc *pmc); int pmc_get_transport_fd(struct pmc *pmc);