/* * Copyright (C) 1996, 1999 Olaf Kirch * Modified by Jeffrey A. Uphoff, 1997-1999. * Modified by H.J. Lu, 1998. * Modified by Lon Hohberger, Oct. 2000 * - Bugfix handling client responses. * - Paranoia on NOTIFY_CALLBACK case * * NSM for Linux. */ /* * After reboot, notify all hosts on our notify list. In order not to * hang statd with delivery to dead hosts, we perform all RPC calls in * parallel. * * It would have been nice to use the portmapper's rmtcall feature, * but that's not possible for security reasons (the portmapper would * have to forward the call with root privs for most statd's, which * it won't if it's worth its money). */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sm_inter.h" #include "statd.h" #include "notlist.h" #include "ha-callout.h" #include "nsm.h" #include "nfsrpc.h" #if SIZEOF_SOCKLEN_T - 0 == 0 #define socklen_t int #endif static int sockfd = -1; /* notify socket */ /* * Initialize socket used to notify lockd of peer reboots. * * Returns the file descriptor of the new socket if successful; * otherwise returns -1 and logs an error. * * Lockd rejects such requests if the source port is not privileged. * statd_get_socket() must be invoked while statd still holds root * privileges in order for the socket to acquire a privileged source * port. */ int statd_get_socket(void) { struct sockaddr_in sin; struct servent *se; const int loopcnt = 100; int i, tmp_sockets[loopcnt]; if (sockfd >= 0) return sockfd; for (i = 0; i < loopcnt; ++i) { if ((sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) { xlog(L_ERROR, "%s: Can't create socket: %m", __func__); break; } memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); if (bindresvport(sockfd, &sin) < 0) { xlog(D_GENERAL, "%s: can't bind to reserved port", __func__); break; } se = getservbyport(sin.sin_port, "udp"); if (se == NULL) break; /* rather not use that port, try again */ tmp_sockets[i] = sockfd; } while (--i >= 0) close(tmp_sockets[i]); if (sockfd < 0) return -1; FD_SET(sockfd, &SVC_FDSET); return sockfd; } static notify_list * recv_rply(u_long *portp) { char msgbuf[NSM_MAXMSGSIZE]; ssize_t msglen; notify_list *lp = NULL; XDR xdr; struct sockaddr_in sin; socklen_t alen = (socklen_t)sizeof(sin); uint32_t xid; memset(msgbuf, 0, sizeof(msgbuf)); msglen = recvfrom(sockfd, msgbuf, sizeof(msgbuf), 0, (struct sockaddr *)(char *)&sin, &alen); if (msglen == (ssize_t)-1) { xlog_warn("%s: recvfrom failed: %m", __func__); return NULL; } memset(&xdr, 0, sizeof(xdr)); xdrmem_create(&xdr, msgbuf, (unsigned int)msglen, XDR_DECODE); xid = nsm_parse_reply(&xdr); if (xid == 0) goto done; if (sin.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) { struct in_addr addr = sin.sin_addr; char buf[INET_ADDRSTRLEN]; xlog_warn("%s: Unrecognized reply from %s", __func__, inet_ntop(AF_INET, &addr, buf, (socklen_t)sizeof(buf))); goto done; } for (lp = notify; lp != NULL; lp = lp->next) { /* LH - this was a bug... it should have been checking * the xid from the response message from the client, * not the static, internal xid */ if (lp->xid != xid) continue; if (lp->port == 0) *portp = nsm_recv_getport(&xdr); break; } done: xdr_destroy(&xdr); return lp; } /* * Notify operation for a single list entry */ static int process_entry(notify_list *lp) { struct sockaddr_in sin; if (NL_TIMES(lp) == 0) { xlog(D_GENERAL, "%s: Cannot notify localhost, giving up", __func__); return 0; } memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = lp->port; /* LH - moved address into switch */ /* __FORCE__ loopback for callbacks to lockd ... */ /* Just in case we somehow ignored it thus far */ sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); if (sin.sin_port == 0) lp->xid = nsm_xmit_getport(sockfd, &sin, (rpcprog_t)NL_MY_PROG(lp), (rpcvers_t)NL_MY_VERS(lp)); else { struct mon m; memcpy(m.priv, NL_PRIV(lp), SM_PRIV_SIZE); m.mon_id.mon_name = NL_MON_NAME(lp); m.mon_id.my_id.my_name = NULL; m.mon_id.my_id.my_prog = NL_MY_PROG(lp); m.mon_id.my_id.my_vers = NL_MY_VERS(lp); m.mon_id.my_id.my_proc = NL_MY_PROC(lp); lp->xid = nsm_xmit_nlmcall(sockfd, (struct sockaddr *)(char *)&sin, (socklen_t)sizeof(sin), &m, NL_STATE(lp)); } if (lp->xid == 0) { xlog_warn("%s: failed to notify port %d", __func__, ntohs(lp->port)); } NL_TIMES(lp) -= 1; return 1; } /* * Process a datagram received on the notify socket */ int process_reply(FD_SET_TYPE *rfds) { notify_list *lp; u_long port; if (sockfd == -1 || !FD_ISSET(sockfd, rfds)) return 0; if (!(lp = recv_rply(&port))) return 1; if (lp->port == 0) { if (port != 0) { lp->port = htons((unsigned short) port); process_entry(lp); NL_WHEN(lp) = time(NULL) + NOTIFY_TIMEOUT; nlist_remove(¬ify, lp); nlist_insert_timer(¬ify, lp); return 1; } xlog_warn("%s: service %d not registered on localhost", __func__, NL_MY_PROG(lp)); } else { xlog(D_GENERAL, "%s: Callback to %s (for %d) succeeded", __func__, NL_MY_NAME(lp), NL_MON_NAME(lp)); } nlist_free(¬ify, lp); return 1; } /* * Process a notify list, either for notifying remote hosts after reboot * or for calling back (local) statd clients when the remote has notified * us of a crash. */ int process_notify_list(void) { notify_list *entry; time_t now; while ((entry = notify) != NULL && NL_WHEN(entry) < time(&now)) { if (process_entry(entry)) { NL_WHEN(entry) = time(NULL) + NOTIFY_TIMEOUT; nlist_remove(¬ify, entry); nlist_insert_timer(¬ify, entry); } else { xlog(L_ERROR, "%s: Can't callback %s (%d,%d), giving up", __func__, NL_MY_NAME(entry), NL_MY_PROG(entry), NL_MY_VERS(entry)); nlist_free(¬ify, entry); } } return 1; }