/* * tivobeacon.c - simple Tivo Media Server beacon * * Copyright 2004 Jesse Barnes * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, as * published by the Free Software Foundation. * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * I saw some perl based Tivo beacons out there, but my machine doesn't * have much memory or CPU power, so I thought a C version would be * better. It's a piece of crap, but seems to work ok for me. * * Since I don't know what one would do with the knowledge of other Media * servers out there, this implementation ignores them. It just blindly * sends its packets out and hopes someone cares about them, and doesn't * bother listening for any other media server packets at all. * * It should work on most Unix like systems (maybe even including cygwin) * but I've only tested it on Linux x86. * * It has to be run on the same machine as your media server since Tivo * will assume that the box generating the beacon packets has something * listening on the service ports specified (currently only one, but * that's easy to change for debugging your own media server). * * To compile it, do: * $ cc -o tivobeacon tivobeacon.c * * And then to run it: * $ ./tivobeacon (it'll fork a background task unless you specify -v) * * And that's it. */ #include #include #include #include #include #include #include #include #include #include const char *name = "tivobeacon"; const char *version = "1.1"; static int verbose; /* global verbose flag */ /* * Allocate and return the platform name, which is the sysname plus * the machine type. E.g. "pc/Linux-i686" (the specs say 'pc' is the * base type of non-Tivo boxes) * * On error, just return either "pc" or NULL. */ static char *get_platform(void) { struct utsname machine_info; char *platform; if (uname(&machine_info)) return strdup("pc"); /* pc/- + '\0' */ platform = malloc(3 + strlen(machine_info.sysname) + 1 + strlen(machine_info.machine) + 1); if (!platform) return strdup("pc"); sprintf(platform, "pc/%s-%s", machine_info.sysname, machine_info.machine); return platform; } /* * Allocate and return a string with the hostname. * * Returns "(none)" or NULL on error. */ static char *get_machine(void) { struct utsname machine_info; char *machine; if (uname(&machine_info)) return strdup("(none)"); /* nodename + '\0' */ machine = malloc(strlen(machine_info.nodename) + 1); if (!machine) return strdup("(none)"); sprintf(machine, "%s", machine_info.nodename); return machine; } /* * Use get_machine for now, maybe MAC address if needed? */ static char *get_identity(void) { return strdup(get_machine()); } /* What other services are there? */ static char *services = "TiVoMediaServer:8101/http"; static char *get_services(void) { return strdup(services); } /* * tivo packet format: * * tivoconnect=\n (should be 1, indicates head of packet)* * method=\n (broadcast or connected for udp or tcp)* * platform=[/]\n (should be pc unless we're on a tivo)* * machine=\n (name of machine, may be empty) * identity=\n (unique identifier for this machine, not ip or dns)* * services=[:][/]\n (list of services, may be blank) * swversion=\n (version string, may be empty) * * A '*' indicates a required field. Broadast packets are sent via UDP * to port 2190. High frequency broadcasts are considered to be once every * 5 seconds or so, while once a minute is considered low frequency. * * In the connected method, TCP port 2190 is opened to the target machine, * and a packet is sent, then the target will send its packet in return. * * When machine information has changed, high frequency broadcasting should * be initiated and run for about 30s. Also, when another machine operating * in high frequency mode is detected, the host should immediately start * broadcasting in high frequency mode for a short time to allow the new * host to quickly discover it. */ static char *create_packet(void) { char method[] = "broadcast"; char *platform = get_platform(); char *machine = get_machine(); char *identity = get_identity(); char *services = get_services(); char *new_packet; int packet_size; if (!method || !platform || !machine || !identity || !services) { fprintf(stderr, "failed to create new packet\n"); exit(1); } packet_size = strlen("tivoconnect=1\n") + strlen("method=\n") + strlen(method) + strlen("platform=\n") + strlen(platform) + strlen("machine=\n") + strlen(machine) + strlen("identity=\n") + strlen(identity) + strlen("services=\n") + strlen(services) + strlen("swversion=\n") + strlen(version); new_packet = malloc(packet_size); if (!new_packet) return NULL; sprintf(new_packet, "tivoconnect=1\n" "method=%s\n" "platform=%s\n" "machine=%s\n" "identity=%s\n" "services=%s\n" "swversion=%s\n", method, platform, machine, identity, services, version); free(platform); free(machine); free(identity); free(services); return new_packet; } #define TIVOBEACONPORT 2190 /* * Infinite broadcast loop */ static void broadcast_loop(char *packet) { int sockfd; struct sockaddr_in sockaddr; int optval = 1, i; sockfd = socket(PF_INET, SOCK_DGRAM, 0); if (sockfd == -1) { perror("failed to create udp socket"); exit(1); } if ((setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &optval, sizeof(optval))) == -1) { perror("failed to set socket broadcast option"); exit(1); } sockaddr.sin_family = AF_INET; sockaddr.sin_port = htons(TIVOBEACONPORT); sockaddr.sin_addr.s_addr = INADDR_BROADCAST; if (bind(sockfd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) == -1) { perror("failed to bind"); exit(1); } /* High frequency broadcast for 30s */ for (i = 0; i < 6; i++) { if ((sendto(sockfd, packet, strlen(packet), 0, (struct sockaddr *)&sockaddr, sizeof(sockaddr))) == -1) perror("failed to send packet\n"); if (verbose) fprintf(stderr, "sent packet:\n\"%s\"\n", packet); sleep(5); } /* Indefinite low frequency broadcast */ while (1) { if ((sendto(sockfd, packet, strlen(packet), 0, (struct sockaddr *)&sockaddr, sizeof(sockaddr))) == -1) { perror("failed to send packet"); } if (verbose) fprintf(stderr, "sent packet:\n\"%s\"\n", packet); sleep(30); } } static void usage(void) { printf("usage: %s [ -v ]\n", name); printf(" -v verbose mode\n"); exit(1); } char *optarg; int optind, opterr, optopt; int main(int argc, char *argv[]) { char *optstr = "v"; char *packet; int opt; while ((opt = getopt(argc, argv, optstr)) != -1) { switch (opt) { case 'v': verbose = 1; break; default: usage(); } } packet = create_packet(); if (!packet) { fprintf(stderr, "out of memory, couldn't create packet\n"); exit(1); } /* Fork a background task unless we're in verbose mode */ if (verbose) broadcast_loop(packet); else if(!fork()) broadcast_loop(packet); free(packet); return 0; }