| #include <errno.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <time.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/stat.h> |
| #include <sys/types.h> |
| |
| #include <fstream> |
| #include <iostream> |
| #include <sstream> |
| #include <string> |
| #include <vector> |
| #include <memory> |
| |
| #include "device_stats.pb.h" |
| |
| #define ETH_PORT "eth0" |
| #define GOOG_PROTOCOL 0x8930 |
| #define STAT_INTERVAL 60 |
| |
| std::string multicast_addr = "FF12::8000:1"; |
| |
| uint8_t mc_mac[] = { 0x01, 0x00, 0x5e, 0x00, 0x00, 0x01 }; |
| const char *optstring = "s:i:a:"; |
| |
| std::string serial_number; |
| std::string wan_interface; |
| std::string acs_contact_file; |
| |
| void usage() { |
| printf("Usage: statpitcher -s <serial number> -i <wan interface> " |
| "-a <acs_contact_file>\n"); |
| exit(1); |
| } |
| |
| void ReadFile(const std::string& fname, std::string* data) { |
| data->clear(); |
| |
| std::ifstream s(fname); |
| if (!s.is_open()) |
| return; |
| data->assign(std::istreambuf_iterator<char>(s), |
| std::istreambuf_iterator<char>()); |
| } |
| |
| int GetIfIndex(int sock) { |
| struct ifreq ifr; |
| memset(&ifr, 0, sizeof(ifr)); |
| snprintf(reinterpret_cast<char*>(ifr.ifr_name), |
| sizeof(ifr.ifr_name), ETH_PORT); |
| if (ioctl(sock, SIOCGIFINDEX, &ifr) != 0) { |
| perror("Failed to get ifindex for ethernet port."); |
| exit(1); |
| } |
| return ifr.ifr_ifindex; |
| } |
| |
| bool WanUp() { |
| std::string if_status; |
| std::string stat_file = "/sys/class/net/" + wan_interface + "/operstate"; |
| ReadFile(stat_file, &if_status); |
| if (if_status.compare(0, 2, "up") == 0) // compare first 2 characters. |
| return true; |
| return false; |
| } |
| |
| // Returns 0 if not contacted. |
| // Otherwise the contact time in seconds since 1970. |
| int64_t AcsContacted() { |
| struct stat stat_buf; |
| memset(&stat_buf, 0, sizeof(stat_buf)); |
| if (stat(acs_contact_file.c_str(), &stat_buf) < 0) |
| return 0; |
| return stat_buf.st_mtime; |
| } |
| |
| int64_t Uptime() { |
| std::string data; |
| float up, idle; |
| ReadFile("/proc/uptime", &data); |
| if (sscanf(data.c_str(), "%f %f", &up, &idle) != 2) { |
| return 0; |
| } |
| return static_cast<int64_t>(up); |
| } |
| |
| std::string IPAddress() { |
| std::ifstream infile; |
| infile.open("/proc/net/if_inet6"); |
| |
| if (!infile.good()) { |
| perror("error reading ipv6 from file"); |
| exit(1); |
| } |
| |
| std::string line; |
| int found = 0; |
| while (!infile.eof()) { |
| getline(infile, line); |
| // Want Ipv6 address on man interface |
| if (line.find("man") == std::string::npos) { |
| continue; |
| } |
| // Avoid local ipv6 |
| if (line.substr(0, 4) == "0100" || // Discard prefix RFC 6666 |
| line.substr(0, 2) == "fc" || // Unique local addresses |
| line.substr(0, 2) == "fd" || |
| line.substr(0, 4) == "fe80" || // Link-local addresses |
| line.substr(0, 4) == "fec0") { // Old, deprecated local address range |
| continue; |
| } |
| found = 1; |
| break; |
| } |
| |
| infile.close(); |
| if (!found || line.size() < 32) { |
| perror("ipv6 address on man not found in file"); |
| return "::1"; |
| } |
| |
| // Add colons |
| std::stringstream ipv6; |
| line = line.substr(0, 32); |
| for (unsigned int i = 0; i < line.size(); i++) { |
| if (i != 0 && i % 4 == 0) { |
| ipv6 << ':'; |
| } |
| ipv6 << line[i]; |
| } |
| |
| // Format canonically |
| struct in6_addr ipv6_struct; |
| if (!inet_pton(AF_INET6, ipv6.str().c_str(), &ipv6_struct)) { |
| std::string errmsg = "unable to parse ipv6 address to inet_pton: " + |
| ipv6.str(); |
| perror(errmsg.c_str()); |
| exit(1); |
| } |
| char address[INET6_ADDRSTRLEN]; |
| if (!inet_ntop(AF_INET6, &ipv6_struct, address, INET6_ADDRSTRLEN)) { |
| std::string errmsg = "unable to parse ipv6 address from inet_pton struct " |
| "created from: " + ipv6.str(); |
| perror(errmsg.c_str()); |
| exit(1); |
| } |
| |
| std::string result(address); |
| return result; |
| } |
| |
| void MakePacket(std::vector<uint8_t>* pkt) { |
| devstatus::Status status; |
| |
| int64_t acs_contact_time = AcsContacted(); |
| |
| status.set_wan_connected(WanUp()); |
| status.set_acs_contacted(acs_contact_time != 0); |
| status.set_acs_contact_time(acs_contact_time); |
| status.set_uptime(Uptime()); |
| status.set_serial(serial_number); |
| status.set_ipv6(IPAddress()); |
| |
| pkt->resize(status.ByteSize()); |
| status.SerializeToArray(&(*pkt)[0], status.ByteSize()); |
| } |
| |
| int MakeSocket() { |
| int sock = socket(PF_INET6, SOCK_DGRAM, 0); |
| if (sock == -1) { |
| perror("can't open socket"); |
| exit(1); |
| } |
| int if_index = GetIfIndex(sock); |
| if (setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_IF, |
| reinterpret_cast<char*>(&if_index), sizeof(if_index)) != 0) { |
| perror("Failed to setsockopt."); |
| 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': |
| wan_interface = std::string(optarg); |
| break; |
| |
| case 's': |
| serial_number = std::string(optarg); |
| break; |
| |
| case 'a': |
| acs_contact_file = std::string(optarg); |
| break; |
| |
| default: |
| printf("Unknown option: %d\n", opt); |
| usage(); |
| break; |
| } |
| } |
| |
| if (wan_interface.empty() || serial_number.empty() || |
| acs_contact_file.empty()) { |
| usage(); |
| return 0; |
| } |
| |
| int sock = MakeSocket(); |
| struct sockaddr_in6 sin6; |
| memset(&sin6, 0, sizeof(sin6)); |
| sin6.sin6_family = AF_INET6; |
| sin6.sin6_port = htons(61453); |
| if (inet_pton(AF_INET6, multicast_addr.c_str(), &sin6.sin6_addr) != 1) { |
| perror("inet_pton failed: "); |
| exit(-1); |
| } |
| |
| for (;;) { |
| pkt.clear(); |
| MakePacket(&pkt); |
| int written = sendto(sock, &pkt[0], pkt.size(), 0, |
| (struct sockaddr*)&sin6, sizeof(sin6)); |
| if (written < 0) { |
| perror("sendto: "); |
| printf("pkt.size()=%d\n", (int)pkt.size()); |
| } |
| sleep(STAT_INTERVAL+1); |
| } |
| return 0; |
| } |