/* * hdhomerun_dhcp.c * * Copyright © 2006-2007 Silicondust Engineering Ltd. . * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * As a special exception to the GNU Lesser General Public License, * you may link, statically or dynamically, an application with a * publicly distributed version of the Library to produce an * executable file containing portions of the Library, and * distribute that executable file under terms of your choice, * without any of the additional requirements listed in clause 4 of * the GNU Lesser General Public License. * * By "a publicly distributed version of the Library", we mean * either the unmodified Library as distributed by Silicondust, or a * modified version of the Library that is distributed under the * conditions defined in the GNU Lesser General Public License. */ #include "hdhomerun.h" #include "hdhomerun_dhcp.h" struct dhcp_hdr_t { uint8_t bootp_message_type; uint8_t hardware_type; uint8_t hardware_address_length; uint8_t hops; uint32_t transaction_id; uint16_t seconds_elapsed; uint16_t bootp_flags; uint32_t client_ip; uint32_t your_ip; uint32_t next_server_ip; uint32_t relay_agent_ip; uint8_t client_mac[16]; uint8_t server_host_name[64]; uint8_t boot_file_name[128]; uint32_t magic_cookie; }; struct hdhomerun_dhcp_t { int sock; uint32_t local_address; pthread_t thread; volatile bool_t terminate; }; static THREAD_FUNC_PREFIX hdhomerun_dhcp_thread_execute(void *arg); struct hdhomerun_dhcp_t *hdhomerun_dhcp_create(uint32_t bind_address) { if (bind_address != 0) { if ((bind_address & 0xFFFF0000) != 0xA9FE0000) { return NULL; } } /* Create socket. */ int sock = (int)socket(AF_INET, SOCK_DGRAM, 0); if (sock == -1) { return NULL; } /* Set timeout. */ setsocktimeout(sock, SOL_SOCKET, SO_RCVTIMEO, 1000); /* Allow broadcast. */ int sock_opt = 1; setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char *)&sock_opt, sizeof(sock_opt)); /* Allow reuse. */ sock_opt = 1; setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&sock_opt, sizeof(sock_opt)); /* Bind socket. */ struct sockaddr_in sock_addr; memset(&sock_addr, 0, sizeof(sock_addr)); sock_addr.sin_family = AF_INET; sock_addr.sin_addr.s_addr = htonl(bind_address); sock_addr.sin_port = htons(67); if (bind(sock, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) != 0) { close(sock); return NULL; } /* Allocate object. */ struct hdhomerun_dhcp_t *dhcp = (struct hdhomerun_dhcp_t *)calloc(1, sizeof(struct hdhomerun_dhcp_t)); if (!dhcp) { close(sock); return NULL; } dhcp->sock = sock; if (bind_address != 0) { dhcp->local_address = bind_address; } else { dhcp->local_address = 0xA9FEFFFF; } /* Spawn thread. */ if (pthread_create(&dhcp->thread, NULL, &hdhomerun_dhcp_thread_execute, dhcp) != 0) { close(sock); free(dhcp); return NULL; } /* Success. */ return dhcp; } void hdhomerun_dhcp_destroy(struct hdhomerun_dhcp_t *dhcp) { dhcp->terminate = TRUE; pthread_join(dhcp->thread, NULL); close(dhcp->sock); free(dhcp); } static void hdhomerun_dhcp_send(struct hdhomerun_dhcp_t *dhcp, uint8_t message_type, struct hdhomerun_pkt_t *pkt) { pkt->pos = pkt->start; struct dhcp_hdr_t *hdr = (struct dhcp_hdr_t *)pkt->pos; pkt->pos += sizeof(struct dhcp_hdr_t); pkt->end = pkt->pos; uint32_t remote_addr = 0xA9FE0000; remote_addr |= (uint32_t)hdr->client_mac[4] << 8; remote_addr |= (uint32_t)hdr->client_mac[5] << 0; if ((remote_addr == 0xA9FE0000) || (remote_addr == 0xA9FEFFFF)) { remote_addr = 0xA9FE8080; } hdr->bootp_message_type = 0x02; hdr->your_ip = htonl(remote_addr); hdr->next_server_ip = htonl(0x00000000); hdhomerun_pkt_write_u8(pkt, 53); hdhomerun_pkt_write_u8(pkt, 1); hdhomerun_pkt_write_u8(pkt, message_type); hdhomerun_pkt_write_u8(pkt, 54); hdhomerun_pkt_write_u8(pkt, 4); hdhomerun_pkt_write_u32(pkt, dhcp->local_address); hdhomerun_pkt_write_u8(pkt, 51); hdhomerun_pkt_write_u8(pkt, 4); hdhomerun_pkt_write_u32(pkt, 7*24*60*60); hdhomerun_pkt_write_u8(pkt, 1); hdhomerun_pkt_write_u8(pkt, 4); hdhomerun_pkt_write_u32(pkt, 0xFFFF0000); hdhomerun_pkt_write_u8(pkt, 0xFF); while (pkt->pos < pkt->start + 300) { hdhomerun_pkt_write_u8(pkt, 0x00); } struct sockaddr_in sock_addr; memset(&sock_addr, 0, sizeof(sock_addr)); sock_addr.sin_family = AF_INET; sock_addr.sin_addr.s_addr = htonl(0xFFFFFFFF); sock_addr.sin_port = htons(68); sendto(dhcp->sock, (char *)pkt->start, (int)(pkt->end - pkt->start), 0, (struct sockaddr *)&sock_addr, sizeof(sock_addr)); } static void hdhomerun_dhcp_recv(struct hdhomerun_dhcp_t *dhcp, struct hdhomerun_pkt_t *pkt) { pkt->pos = pkt->start; struct dhcp_hdr_t *hdr = (struct dhcp_hdr_t *)pkt->pos; pkt->pos += sizeof(struct dhcp_hdr_t); if (pkt->pos > pkt->end) { return; } if (ntohl(hdr->magic_cookie) != 0x63825363) { return; } static uint8_t vendor[3] = {0x00, 0x18, 0xDD}; if (memcmp(hdr->client_mac, vendor, 3) != 0) { return; } if (pkt->pos + 3 > pkt->end) { return; } if (hdhomerun_pkt_read_u8(pkt) != 53) { return; } if (hdhomerun_pkt_read_u8(pkt) != 1) { return; } uint8_t message_type_val = hdhomerun_pkt_read_u8(pkt); switch (message_type_val) { case 0x01: hdhomerun_dhcp_send(dhcp, 0x02, pkt); break; case 0x03: hdhomerun_dhcp_send(dhcp, 0x05, pkt); break; default: return; } } static THREAD_FUNC_PREFIX hdhomerun_dhcp_thread_execute(void *arg) { struct hdhomerun_dhcp_t *dhcp = (struct hdhomerun_dhcp_t *)arg; struct hdhomerun_pkt_t pkt_inst; while (1) { if (dhcp->terminate) { return NULL; } struct hdhomerun_pkt_t *pkt = &pkt_inst; hdhomerun_pkt_reset(pkt); int rx_length = recv(dhcp->sock, (char *)pkt->end, (int)(pkt->limit - pkt->end), 0); if (rx_length <= 0) { if (!sock_getlasterror_socktimeout) { sleep(1); } continue; } pkt->end += rx_length; hdhomerun_dhcp_recv(dhcp, pkt); } }