blob: f8f270cb214baa3e195637aff1d2b758e207d685 [file] [log] [blame]
// The ONU periodically multicasts out its own status, this program
// listens for those multicasts and writes the status out to a file that
// be read in by catawampus and displayed on the diagnostic page.
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <net/if.h>
#include <netpacket/packet.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
#include "device_stats.pb.h"
std::string multicast_addr = "FF30::8000:1";
const char *optstring = "i:f:";
std::string interface = "wan0";
std::string stat_file;
std::string tmp_file;
void usage() {
printf("Usage: statcatcher -i <interface> -f <stat file>\n");
exit(1);
}
int GetIfIndex(int sock, const std::string& port_name) {
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
snprintf(reinterpret_cast<char*>(ifr.ifr_name),
sizeof(ifr.ifr_name), "%s", port_name.c_str());
if (ioctl(sock, SIOCGIFINDEX, &ifr) != 0) {
perror("Failed to get ifindex for ethernet port.");
exit(1);
}
return ifr.ifr_ifindex;
}
int MakeSocket() {
int sock = socket(PF_INET6, SOCK_DGRAM, 0);
if (sock == -1) {
perror("can't open socket");
exit(1);
}
struct sockaddr_in6 in6;
memset(&in6, 0, sizeof(in6));
in6.sin6_family = PF_INET6;
in6.sin6_port = htons(61453);
in6.sin6_addr = in6addr_any;
if (bind(sock, (struct sockaddr *)&in6, sizeof(in6)) < 0) {
perror("bind failed");
exit(1);
}
// Join group
struct ipv6_mreq mc_req;
memset(&mc_req, 0, sizeof(mc_req));
if (inet_pton(AF_INET6,
multicast_addr.c_str(), &mc_req.ipv6mr_multiaddr) != 1) {
printf("multicast_addr='%s'\n", multicast_addr.c_str());
perror("Could not convert multicast_addr");
exit(1);
}
mc_req.ipv6mr_interface = GetIfIndex(sock, interface);
if (setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP,
reinterpret_cast<char*>(&mc_req), sizeof(mc_req)) != 0) {
perror("unable to join ipv6 group");
exit(1);
}
return sock;
}
int main(int argc, char** argv) {
std::vector<uint8_t> pkt;
int opt;
while ((opt = getopt(argc, argv, optstring)) != -1) {
switch (opt) {
case 'i':
interface = std::string(optarg);
break;
case 'f':
stat_file = std::string(optarg);
tmp_file = stat_file + ".tmp";
break;
default:
usage();
break;
}
}
if (stat_file.empty() || interface.empty()) {
usage();
exit(1);
}
int sock = MakeSocket();
pkt.resize(2048);
int recvsize = 0;
for (;;) {
// process only 1 message per second to prevent a dos attack.
sleep(1);
pkt.resize(2048);
recvsize = recv(sock, &pkt[0], 2048, 0);
if (recvsize < 0) {
perror("Failed to receive data on socket.\n");
exit(1);
}
pkt.resize(recvsize);
// Deserialize message.
devstatus::Status status;
if (!status.ParseFromArray(&pkt[0], pkt.size())) {
printf("failed to parse received data.");
continue;
}
// This is C++0x raw string formatting.
// NOTE(jnewlin): There are some spiffy automatic proto to json
// converters, if we add more data we might want to get rid of this
// simplistic converter and use something like that. Either that or just
// write the proto and make catawampus read the proto, there was
// hesitation to adding proto support to cwmp.
std::string json_out = R"({
"onu_wan_connected": %s,
"onu_acs_contacted": %s,
"onu_acs_contact_time": "%lld",
"onu_uptime": %lld,
"onu_serial": "%s"
})";
FILE *f = fopen(tmp_file.c_str(), "w");
if (!f) {
printf("Can't open tmp file for writing.");
exit(1);
}
fprintf(f, json_out.c_str(),
status.wan_connected() ? "true" : "false",
status.acs_contacted() ? "true" : "false",
status.acs_contact_time(),
status.uptime(),
status.serial().c_str());
fclose(f);
if (rename(tmp_file.c_str(), stat_file.c_str()) != 0) {
perror("rename tmp file failed.");
}
}
return 0;
}