| /** |
| * @file nsm.c |
| * @brief NSM client program |
| * @note Copyright (C) 2018 Richard Cochran <richardcochran@gmail.com> |
| * |
| * 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. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License along |
| * with this program; if not, write to the Free Software Foundation, Inc., |
| * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| */ |
| #include <errno.h> |
| #include <poll.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <inttypes.h> |
| #include <arpa/inet.h> |
| |
| #include "config.h" |
| #include "print.h" |
| #include "rtnl.h" |
| #include "sad.h" |
| #include "util.h" |
| #include "version.h" |
| |
| #define IFMT "\n\t\t" |
| #define NSM_NFD 3 |
| |
| struct interface { |
| STAILQ_ENTRY(interface) list; |
| }; |
| |
| struct nsm { |
| struct config *cfg; |
| struct fdarray fda; |
| struct transport *trp; |
| struct tsproc *tsproc; |
| struct ptp_message *nsm_delay_req; |
| struct ptp_message *nsm_delay_resp; |
| struct ptp_message *nsm_sync; |
| struct ptp_message *nsm_fup; |
| struct PortIdentity port_identity; |
| UInteger16 sequence_id; |
| const char *name; |
| int spp; |
| UInteger32 active_key_id; |
| } the_nsm; |
| |
| static void nsm_help(FILE *fp); |
| static int nsm_request(struct nsm *nsm, char *target); |
| static void nsm_reset(struct nsm *nsm); |
| |
| static int nsm_command(struct nsm *nsm, const char *cmd) |
| { |
| char action_str[10+1] = {0}, id_str[64+1] = {0}; |
| |
| if (0 == strncasecmp(cmd, "HELP", strlen(cmd))) { |
| nsm_help(stdout); |
| return 0; |
| } |
| if (2 != sscanf(cmd, " %10s %64s", action_str, id_str)) { |
| pr_err("bad command: %s", cmd); |
| return -1; |
| } |
| if (0 == strncasecmp(action_str, "NSM", strlen(action_str))) { |
| return nsm_request(nsm, id_str); |
| } |
| pr_err("bad command: %s", cmd); |
| return -1; |
| } |
| |
| static int nsm_complete(struct nsm *nsm) |
| { |
| if (!nsm->nsm_sync) { |
| return 0; |
| } |
| if (one_step(nsm->nsm_sync)) { |
| return nsm->nsm_delay_resp ? 1 : 0; |
| } |
| return (nsm->nsm_delay_resp && nsm->nsm_fup) ? 1 : 0; |
| } |
| |
| static int64_t nsm_compute_offset(struct tsproc *tsp, |
| struct ptp_message *syn, |
| struct ptp_message *fup, |
| struct ptp_message *req, |
| struct ptp_message *resp) |
| { |
| tmv_t c1, c2, c3, t1, t1c, t2, t3, t4, t4c, offset; |
| |
| c1 = correction_to_tmv(syn->header.correction); |
| c2 = correction_to_tmv(fup->header.correction); |
| c3 = correction_to_tmv(resp->header.correction); |
| |
| t1 = timestamp_to_tmv(fup->ts.pdu); |
| t2 = syn->hwts.ts; |
| t3 = req->hwts.ts; |
| t4 = timestamp_to_tmv(resp->ts.pdu); |
| |
| t1c = tmv_add(t1, tmv_add(c1, c2)); |
| t4c = tmv_sub(t4, c3); |
| |
| tsproc_reset(tsp, 1); |
| tsproc_down_ts(tsp, t1c, t2); |
| tsproc_up_ts(tsp, t3, t4c); |
| tsproc_update_offset(tsp, &offset, NULL); |
| |
| return tmv_to_nanoseconds(offset); |
| } |
| |
| static void nsm_close(struct nsm *nsm) |
| { |
| nsm_reset(nsm); |
| transport_close(nsm->trp, &nsm->fda); |
| transport_destroy(nsm->trp); |
| tsproc_destroy(nsm->tsproc); |
| } |
| |
| static void nsm_handle_msg(struct nsm *nsm, struct ptp_message *msg, FILE *fp) |
| { |
| struct nsm_resp_tlv_head *head; |
| struct nsm_resp_tlv_foot *foot; |
| struct timePropertiesDS *tp; |
| struct PortAddress *paddr; |
| struct currentDS cds; |
| struct parentDS *pds; |
| struct Timestamp ts; |
| unsigned char *ptr; |
| int64_t offset; |
| |
| if (!nsm->nsm_delay_req) { |
| return; |
| } |
| if (msg->header.sequenceId != |
| ntohs(nsm->nsm_delay_req->header.sequenceId)) { |
| return; |
| } |
| if (!msg_unicast(msg)) { |
| return; |
| } |
| |
| switch (msg_type(msg)) { |
| case SYNC: |
| if (!nsm->nsm_sync) { |
| nsm->nsm_sync = msg; |
| msg_get(msg); |
| } |
| break; |
| case FOLLOW_UP: |
| if (!nsm->nsm_fup) { |
| nsm->nsm_fup = msg; |
| msg_get(msg); |
| } |
| break; |
| case DELAY_RESP: |
| if (!nsm->nsm_delay_resp) { |
| nsm->nsm_delay_resp = msg; |
| msg_get(msg); |
| } |
| break; |
| case DELAY_REQ: |
| case PDELAY_REQ: |
| case PDELAY_RESP: |
| case PDELAY_RESP_FOLLOW_UP: |
| case ANNOUNCE: |
| case SIGNALING: |
| case MANAGEMENT: |
| return; |
| } |
| |
| if (!nsm_complete(nsm)) { |
| return; |
| } |
| |
| head = (struct nsm_resp_tlv_head *) nsm->nsm_delay_resp->delay_resp.suffix; |
| paddr = &head->parent_addr; |
| |
| ptr = (unsigned char *) head; |
| ptr += sizeof(*head) + paddr->addressLength; |
| foot = (struct nsm_resp_tlv_foot *) ptr; |
| |
| pds = &foot->parent; |
| memcpy(&cds, &foot->current, sizeof(cds)); |
| tp = &foot->timeprop; |
| memcpy(&ts, &foot->lastsync, sizeof(ts)); |
| |
| offset = nsm_compute_offset(nsm->tsproc, nsm->nsm_sync, nsm->nsm_fup, |
| nsm->nsm_delay_req, nsm->nsm_delay_resp); |
| |
| fprintf(fp, "NSM MEASUREMENT COMPLETE" |
| IFMT "offset %" PRId64 |
| IFMT "portState %s" |
| IFMT "parentPortAddress %hu %s\n", |
| offset, |
| ps_str[head->port_state], |
| head->parent_addr.networkProtocol, |
| portaddr2str(&head->parent_addr)); |
| fprintf(fp, "\tparentDataset" |
| IFMT "parentPortIdentity %s" |
| IFMT "parentStats %hhu" |
| IFMT "observedParentOffsetScaledLogVariance 0x%04hx" |
| IFMT "observedParentClockPhaseChangeRate 0x%08x" |
| IFMT "grandmasterPriority1 %hhu" |
| IFMT "gm.ClockClass %hhu" |
| IFMT "gm.ClockAccuracy 0x%02hhx" |
| IFMT "gm.OffsetScaledLogVariance 0x%04hx" |
| IFMT "grandmasterPriority2 %hhu" |
| IFMT "grandmasterIdentity %s\n", |
| pid2str(&pds->parentPortIdentity), |
| pds->parentStats, |
| pds->observedParentOffsetScaledLogVariance, |
| pds->observedParentClockPhaseChangeRate, |
| pds->grandmasterPriority1, |
| pds->grandmasterClockQuality.clockClass, |
| pds->grandmasterClockQuality.clockAccuracy, |
| pds->grandmasterClockQuality.offsetScaledLogVariance, |
| pds->grandmasterPriority2, |
| cid2str(&pds->grandmasterIdentity)); |
| fprintf(fp, "\tcurrentDataset" |
| IFMT "stepsRemoved %hd" |
| IFMT "offsetFromMaster %.1f" |
| IFMT "meanPathDelay %.1f\n", |
| cds.stepsRemoved, cds.offsetFromMaster / 65536.0, |
| cds.meanPathDelay / 65536.0); |
| fprintf(fp, "\ttimePropertiesDataset" |
| IFMT "currentUtcOffset %hd" |
| IFMT "leap61 %d" |
| IFMT "leap59 %d" |
| IFMT "currentUtcOffsetValid %d" |
| IFMT "ptpTimescale %d" |
| IFMT "timeTraceable %d" |
| IFMT "frequencyTraceable %d" |
| IFMT "timeSource 0x%02hhx\n", |
| tp->currentUtcOffset, |
| tp->flags & LEAP_61 ? 1 : 0, |
| tp->flags & LEAP_59 ? 1 : 0, |
| tp->flags & UTC_OFF_VALID ? 1 : 0, |
| tp->flags & PTP_TIMESCALE ? 1 : 0, |
| tp->flags & TIME_TRACEABLE ? 1 : 0, |
| tp->flags & FREQ_TRACEABLE ? 1 : 0, |
| tp->timeSource); |
| fprintf(fp, "\tlastSyncTimestamp %" PRId64 ".%09u\n", |
| ((uint64_t)ts.seconds_lsb) | (((uint64_t)ts.seconds_msb) << 32), |
| ts.nanoseconds); |
| |
| fflush(fp); |
| nsm_reset(nsm); |
| } |
| |
| static void nsm_help(FILE *fp) |
| { |
| fprintf(fp, "\tSend a NetSync Monitor request to a specific port address:\n"); |
| fprintf(fp, "\n"); |
| fprintf(fp, "\tNSM 111.222.333.444\n"); |
| fprintf(fp, "\tNSM aa:bb:cc:dd:ee:ff\n"); |
| fprintf(fp, "\n"); |
| } |
| |
| static int nsm_open(struct nsm *nsm, struct config *cfg) |
| { |
| enum transport_type transport; |
| char ts_label[IF_NAMESIZE]; |
| const char *ifname, *name; |
| struct interface *iface; |
| int count = 0; |
| |
| STAILQ_FOREACH(iface, &cfg->interfaces, list) { |
| ifname = interface_name(iface); |
| memset(ts_label, 0, sizeof(ts_label)); |
| if (!rtnl_get_ts_device(ifname, ts_label)) |
| interface_set_label(iface, ts_label); |
| count++; |
| } |
| if (count != 1) { |
| pr_err("need exactly one interface"); |
| return -1; |
| } |
| iface = STAILQ_FIRST(&cfg->interfaces); |
| nsm->name = name = interface_name(iface); |
| nsm->cfg = cfg; |
| nsm->spp = config_get_int(cfg, name, "spp"); |
| nsm->active_key_id = config_get_uint(cfg, name, "active_key_id"); |
| if (sad_readiness_check(nsm->spp, nsm->active_key_id, nsm->cfg)) { |
| return -1; |
| } |
| |
| transport = config_get_int(cfg, name, "network_transport"); |
| |
| if (generate_clock_identity(&nsm->port_identity.clockIdentity, name)) { |
| pr_err("failed to generate a clock identity"); |
| return -1; |
| } |
| nsm->port_identity.portNumber = 1; |
| |
| nsm->tsproc = tsproc_create(TSPROC_RAW, FILTER_MOVING_AVERAGE, 10); |
| if (!nsm->tsproc) { |
| pr_err("failed to create time stamp processor"); |
| goto no_tsproc; |
| } |
| nsm->trp = transport_create(cfg, transport); |
| if (!nsm->trp) { |
| pr_err("failed to create transport"); |
| goto no_trans; |
| } |
| if (transport_open(nsm->trp, iface, &nsm->fda, |
| config_get_int(cfg, NULL, "time_stamping"))) { |
| pr_err("failed to open transport"); |
| goto open_failed; |
| } |
| return 0; |
| |
| open_failed: |
| transport_destroy(nsm->trp); |
| no_trans: |
| tsproc_destroy(nsm->tsproc); |
| no_tsproc: |
| return -1; |
| } |
| |
| static struct ptp_message *nsm_recv(struct nsm *nsm, int fd) |
| { |
| struct ptp_message *msg, *dup = NULL; |
| int cnt, err; |
| |
| msg = msg_allocate(); |
| if (!msg) { |
| pr_err("low memory"); |
| return NULL; |
| } |
| msg->hwts.type = config_get_int(nsm->cfg, NULL, "time_stamping"); |
| |
| cnt = transport_recv(nsm->trp, fd, msg); |
| if (cnt <= 0) { |
| pr_err("recv message failed"); |
| goto failed; |
| } |
| if (nsm->spp >= 0) { |
| dup = msg_duplicate(msg, 0); |
| if (!dup) { |
| goto failed; |
| } |
| } |
| err = msg_post_recv(msg, cnt); |
| if (err) { |
| switch (err) { |
| case -EBADMSG: |
| pr_err("bad message"); |
| break; |
| case -EPROTO: |
| pr_debug("ignoring message"); |
| break; |
| } |
| goto failed; |
| } |
| if (msg_sots_missing(msg)) { |
| pr_err("received %s without timestamp", |
| msg_type_string(msg_type(msg))); |
| goto failed; |
| } |
| err = sad_process_auth(nsm->cfg, nsm->spp, msg, dup); |
| if (err) { |
| switch (err) { |
| case -EBADMSG: |
| pr_err("auth: bad message"); |
| break; |
| case -EPROTO: |
| pr_debug("auth: ignoring message"); |
| break; |
| } |
| goto failed; |
| } |
| if (dup) { |
| msg_put(dup); |
| } |
| return msg; |
| failed: |
| msg_put(msg); |
| if (dup) { |
| msg_put(dup); |
| } |
| return NULL; |
| } |
| |
| static int nsm_request(struct nsm *nsm, char *target) |
| { |
| enum transport_type type = transport_type(nsm->trp); |
| UInteger8 transportSpecific; |
| struct ptp_message *msg; |
| struct tlv_extra *extra; |
| Integer64 asymmetry; |
| struct address dst; |
| int cnt, err; |
| |
| if (str2addr(type, target, &dst)) { |
| return -1; |
| } |
| |
| msg = msg_allocate(); |
| if (!msg) { |
| return -1; |
| } |
| |
| transportSpecific = config_get_int(nsm->cfg, nsm->name, "transportSpecific"); |
| transportSpecific <<= 4; |
| |
| asymmetry = config_get_int(nsm->cfg, nsm->name, "delayAsymmetry"); |
| asymmetry <<= 16; |
| |
| msg->hwts.type = config_get_int(nsm->cfg, NULL, "time_stamping"); |
| |
| msg->header.tsmt = DELAY_REQ | transportSpecific; |
| msg->header.ver = PTP_VERSION; |
| msg->header.messageLength = sizeof(struct delay_req_msg); |
| msg->header.domainNumber = config_get_int(nsm->cfg, NULL, "domainNumber"); |
| msg->header.correction = -asymmetry; |
| msg->header.sourcePortIdentity = nsm->port_identity; |
| msg->header.sequenceId = nsm->sequence_id++; |
| msg->header.logMessageInterval = 0x7f; |
| |
| msg->address = dst; |
| msg->header.flagField[0] |= UNICAST; |
| |
| extra = msg_tlv_append(msg, sizeof(struct TLV)); |
| if (!extra) { |
| msg_put(msg); |
| return -ENOMEM; |
| } |
| extra->tlv->type = TLV_PTPMON_REQ; |
| extra->tlv->length = 0; |
| |
| if (nsm->spp >= 0) { |
| err = sad_append_auth_tlv(nsm->cfg, nsm->spp, |
| nsm->active_key_id, msg); |
| } else { |
| err = msg_pre_send(msg); |
| } |
| if (err) { |
| pr_err("msg_pre_send failed"); |
| goto out; |
| } |
| cnt = transport_sendto(nsm->trp, &nsm->fda, TRANS_EVENT, msg); |
| if (cnt <= 0) { |
| pr_err("transport_sendto failed"); |
| err = -1; |
| goto out; |
| } |
| if (msg_sots_missing(msg)) { |
| pr_err("missing timestamp on transmitted delay request"); |
| err = -1; |
| goto out; |
| } |
| nsm_reset(nsm); |
| nsm->nsm_delay_req = msg; |
| return 0; |
| out: |
| msg_put(msg); |
| return err; |
| } |
| |
| static void nsm_reset(struct nsm *nsm) |
| { |
| if (nsm->nsm_delay_req) { |
| msg_put(nsm->nsm_delay_req); |
| } |
| if (nsm->nsm_delay_resp) { |
| msg_put(nsm->nsm_delay_resp); |
| } |
| if (nsm->nsm_sync) { |
| msg_put(nsm->nsm_sync); |
| } |
| if (nsm->nsm_fup) { |
| msg_put(nsm->nsm_fup); |
| } |
| nsm->nsm_delay_req = NULL; |
| nsm->nsm_delay_resp = NULL; |
| nsm->nsm_sync = NULL; |
| nsm->nsm_fup = NULL; |
| } |
| |
| static void usage(char *progname) |
| { |
| fprintf(stderr, |
| "\nusage: %s [options]\n\n" |
| " -f [file] read configuration from 'file'\n" |
| " -h prints this message and exits\n" |
| " -i [dev] interface device to use\n" |
| " -v prints the software version and exits\n" |
| "\n", |
| progname); |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| int batch_mode = 0, c, cnt, err = 0, index, length, tmo = -1; |
| char *cmd = NULL, *config = NULL, line[1024], *progname; |
| struct pollfd pollfd[NSM_NFD]; |
| struct nsm *nsm = &the_nsm; |
| struct ptp_message *msg; |
| struct option *opts; |
| struct config *cfg; |
| |
| if (handle_term_signals()) { |
| return -1; |
| } |
| cfg = config_create(); |
| if (!cfg) { |
| return -1; |
| } |
| opts = config_long_options(cfg); |
| print_set_verbose(1); |
| print_set_syslog(0); |
| |
| /* Process the command line arguments. */ |
| progname = strrchr(argv[0], '/'); |
| progname = progname ? 1+progname : argv[0]; |
| while (EOF != (c = getopt_long(argc, argv, "f:hi:v", opts, &index))) { |
| switch (c) { |
| case 0: |
| if (config_parse_option(cfg, opts[index].name, optarg)) { |
| config_destroy(cfg); |
| return -1; |
| } |
| break; |
| case 'f': |
| config = optarg; |
| break; |
| case 'i': |
| if (!config_create_interface(optarg, cfg)) { |
| config_destroy(cfg); |
| return -1; |
| } |
| break; |
| case 'v': |
| version_show(stdout); |
| config_destroy(cfg); |
| return 0; |
| case 'h': |
| usage(progname); |
| config_destroy(cfg); |
| return 0; |
| case '?': |
| default: |
| usage(progname); |
| config_destroy(cfg); |
| return -1; |
| } |
| } |
| |
| print_set_syslog(0); |
| print_set_verbose(1); |
| |
| if (config && (err = config_read(config, cfg))) { |
| goto out; |
| } |
| |
| print_set_progname(progname); |
| print_set_tag(config_get_string(cfg, NULL, "message_tag")); |
| print_set_level(config_get_int(cfg, NULL, "logging_level")); |
| |
| if (sad_create(cfg)) { |
| goto out; |
| } |
| |
| err = nsm_open(nsm, cfg); |
| if (err) { |
| goto out; |
| } |
| |
| if (optind < argc) { |
| batch_mode = 1; |
| } |
| |
| pollfd[0].fd = nsm->fda.fd[0]; |
| pollfd[1].fd = nsm->fda.fd[1]; |
| pollfd[2].fd = batch_mode ? -1 : STDIN_FILENO; |
| pollfd[0].events = POLLIN | POLLPRI; |
| pollfd[1].events = POLLIN | POLLPRI; |
| pollfd[2].events = batch_mode ? 0 : POLLIN | POLLPRI; |
| |
| while (is_running()) { |
| if (batch_mode) { |
| if (optind < argc && !nsm->nsm_delay_req) { |
| cmd = argv[optind++]; |
| if (nsm_command(nsm, cmd)) { |
| pr_err("command failed"); |
| continue; |
| } |
| } |
| /* Wait a bit for any outstanding replies. */ |
| tmo = 100; |
| } |
| |
| cnt = poll(pollfd, NSM_NFD, tmo); |
| if (cnt < 0) { |
| if (EINTR == errno) { |
| continue; |
| } else { |
| pr_emerg("poll failed"); |
| err = -1; |
| break; |
| } |
| } else if (!cnt && optind < argc) { |
| /* For batch mode. No response received from target node, |
| * continue with next command. */ |
| nsm_reset(nsm); |
| continue; |
| } else if (!cnt) { |
| break; |
| } |
| if (pollfd[2].revents & POLLHUP) { |
| if (tmo == -1) { |
| /* Wait a bit longer for outstanding replies. */ |
| tmo = 100; |
| pollfd[2].fd = -1; |
| pollfd[2].events = 0; |
| } else { |
| break; |
| } |
| } |
| if (pollfd[2].revents & (POLLIN|POLLPRI)) { |
| if (!fgets(line, sizeof(line), stdin)) { |
| break; |
| } |
| length = strlen(line); |
| if (length < 2) { |
| continue; |
| } |
| line[length - 1] = 0; |
| cmd = line; |
| if (nsm_command(nsm, cmd)) { |
| pr_err("command failed"); |
| } |
| } |
| if (pollfd[0].revents & (POLLIN|POLLPRI)) { |
| msg = nsm_recv(nsm, pollfd[0].fd); |
| if (msg) { |
| nsm_handle_msg(nsm, msg, stdout); |
| msg_put(msg); |
| } |
| } |
| if (pollfd[1].revents & (POLLIN|POLLPRI)) { |
| msg = nsm_recv(nsm, pollfd[1].fd); |
| if (msg) { |
| nsm_handle_msg(nsm, msg, stdout); |
| msg_put(msg); |
| } |
| } |
| } |
| |
| nsm_close(nsm); |
| out: |
| msg_cleanup(); |
| sad_destroy(cfg); |
| config_destroy(cfg); |
| return err; |
| } |