├── .gitignore ├── .gitmodules ├── TODO ├── channels.conf-example ├── Makefile ├── web_server.h ├── web_pages.h ├── ChangeLog ├── README ├── README.WEB_ACCESS ├── config.h ├── bitstream.h ├── web_server.c ├── web_pages.c ├── COPYING └── tomcast.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | tomcast 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libfuncs"] 2 | path = libfuncs 3 | url = git://github.com/gfto/libfuncs.git 4 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | - Write documentation. 2 | - Write man page. 3 | - Add install/uninstall targets in Makefile. 4 | - Add long options using getopt_long. 5 | - Add RTP input. 6 | - Add RTP output. 7 | - Add RELEASE file and use it for versioning. 8 | - The code is written in 2010, modernize it... 9 | -------------------------------------------------------------------------------- /channels.conf-example: -------------------------------------------------------------------------------- 1 | ## The config file format is (use TABs not spaces!): 2 | ## 3 | ## channel[tab]dest[tab]source 4 | ## 5 | ## Lines starting with # are considered comments. 6 | 7 | ## Read channel from http://example.com/stb/btv.mpg and output it to multicast address 239.78.78.78:5000 8 | chan1 239.78.78.78:5000 http://example.com/chan1.ts 9 | 10 | ## chan2 have multiple sources. If a source doesn't work, tomcast switches to the next one. 11 | chan2 239.79.79.79:5000 http://example.com/chan2.ts 12 | chan2 239.79.79.79:5000 http://example.com/chan2.ts 13 | chan2 239.79.79.79:5000 http://example.com/chan2.ts 14 | 15 | ## chan3 have multicast source, tomcast just rewrites the multicast group. 16 | chan3 239.80.80.80:5000 udp://239.1.2.3:5000/ 17 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC = cc 2 | STRIP = strip 3 | CROSS := $(TARGET) 4 | CFLAGS := -O2 -ggdb -pipe \ 5 | -W -Wall -Wextra \ 6 | -Wshadow -Wformat-security -Wstrict-prototypes \ 7 | -Wredundant-decls -Wold-style-definition 8 | RM = /bin/rm -f 9 | Q = @ 10 | 11 | LIBS = -lpthread 12 | FUNCS_DIR = libfuncs 13 | FUNCS_LIB = $(FUNCS_DIR)/libfuncs.a 14 | 15 | tomcast_OBJS = tomcast.o web_pages.o web_server.o $(FUNCS_LIB) 16 | 17 | all: tomcast 18 | 19 | $(FUNCS_LIB): 20 | $(Q)echo " MAKE $(FUNCS_LIB)" 21 | $(Q)$(MAKE) -s -C $(FUNCS_DIR) 22 | 23 | tomcast: $(tomcast_OBJS) 24 | $(Q)echo " LINK tomcast" 25 | $(Q)$(CROSS)$(CC) $(CFLAGS) $(tomcast_OBJS) $(LIBS) -o tomcast 26 | 27 | %.o: %.c 28 | $(Q)echo " CC tomcast $<" 29 | $(Q)$(CROSS)$(CC) $(CFLAGS) -c $< 30 | 31 | strip: 32 | $(Q)echo " STRIP tomcast" 33 | $(Q)$(CROSS)$(STRIP) tomcast 34 | 35 | clean: 36 | $(Q)echo " RM $(tomcast_OBJS)" 37 | $(Q)$(RM) $(tomcast_OBJS) tomcast *~ 38 | 39 | distclean: clean 40 | $(Q)$(MAKE) -s -C $(FUNCS_DIR) clean 41 | -------------------------------------------------------------------------------- /web_server.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mptsd internal web server header file 3 | * Copyright (C) 2010-2011 Unix Solutions Ltd. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 2 7 | * as published by the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. 17 | */ 18 | #ifndef WEB_SERVER_H 19 | # define WEB_SERVER_H 20 | 21 | #include "config.h" 22 | 23 | void web_server_start(struct config *conf); 24 | void web_server_stop(struct config *conf); 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /web_pages.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mptsd internal web pages header file 3 | * Copyright (C) 2010-2011 Unix Solutions Ltd. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 2 7 | * as published by the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. 17 | */ 18 | #ifndef WEB_PAGES_H 19 | #define WEB_PAGES_H 20 | 21 | void cmd_index(int clientsock); 22 | void cmd_status(int clientsock); 23 | void cmd_getconfig(int clientsock); 24 | void cmd_reconnect(int clientsock); 25 | void cmd_reload(int clientsock); 26 | void cmd_start(int clientsock, const char *uri); 27 | void cmd_stop(int clientsock, const char *uri); 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | vNEXT | xx xxx xxxx 2 | * Add support for handling of 301/302 HTTP redirects 3 | * Add support for detecting encrypted input and switching sources 4 | if the input is encrypted (use -E option). 5 | * Add support to hibernate channels. The web interface has support 6 | for start/stop commands. Channels can start in hibernation. 7 | 8 | v1.30 | 21 Dec 2016 9 | * Add web access for monitoring and reconfiguration 10 | 11 | v1.20 | 17 Aug 2013 12 | * First publicly released version. 13 | 14 | v1.15 | 13 Sep 2012 15 | * Add option to send reset packets when source have been reconnected. 16 | These packets prevent VLC from hanging. 17 | 18 | v1.14 | 16 May 2012 19 | * Set send buffer size. 20 | 21 | v1.13 | 01 Mar 2012 22 | * Use mpeg_sync on connect. 23 | * Update libfuncs to latest version from upstream repo. 24 | 25 | v1.12 | 10 Dec 2010 26 | * Allow URL to contain anything 27 | 28 | v1.11 | 21 Jul 2010 29 | * Send X-Smart-Client: yes in GET request 30 | 31 | v1.10 | 24 Feb 2010 32 | * Check for read errors and reconnect 33 | * Check for write errors and reconnect udp socket if there are any 34 | 35 | v1.00 | 09 Feb 2010 36 | * First version. 37 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | tomcast 2 | ======= 3 | tomcast reads mpeg transport streams over http or udp (multicast or 4 | unicast) and outputs them to chosen multicast group. Basically a simple 5 | http2multicast daemon designed to work 24/7. 6 | 7 | License 8 | ======= 9 | tsdecrypt is released under GNU GPL v2. 10 | 11 | Development 12 | =========== 13 | The development is done using git. tomcast repository is hosted 14 | at http://github.com/gfto/tomcast 15 | 16 | To clone the repository issue the following commands: 17 | 18 | git clone git://github.com/gfto/tomcast.git 19 | cd tomcast 20 | git submodule init 21 | git submodule update 22 | make 23 | 24 | The code is developed and tested under modern Linux. It is also 25 | compiled from time to time under OS X but is not tested there. 26 | 27 | To see all Makefile targets run 'make help'. 28 | 29 | Updating the code 30 | ================= 31 | To update cloned tomcast, go to the directory where the repository 32 | is cloned and run the following commands: 33 | 34 | git fetch origin 35 | git merge origin/master 36 | git submodule update 37 | make clean all 38 | 39 | tomcast's master branch should always be useful so it is safe to 40 | use it instead of official release. The master branch will always 41 | be better than any released version. 42 | 43 | Releases 44 | ======== 45 | Official releases can be downloaded from tomcast home page which is 46 | 47 | http://georgi.unixsol.org/programs/tomcast/ 48 | 49 | Contact 50 | ======= 51 | For patches, bug reports, complaints and so on send e-mail to 52 | 53 | Georgi Chorbadzhiyski 54 | -------------------------------------------------------------------------------- /README.WEB_ACCESS: -------------------------------------------------------------------------------- 1 | To enable the web access start tomcast with -b (listen address) and 2 | -p (listen port parameters). 3 | 4 | For example: tomcast -b 127.0.0.1 -p 8081 5 | 6 | Now you can access the web interface exposed by tomcast at 7 | 8 | http://127.0.0.1:8081/ 9 | 10 | The following endpoints are accessible: 11 | 12 | http://127.0.0.1:8081/getconfig 13 | 14 | Return currently configured channels in channels.conf format 15 | that tomcast uses. 16 | 17 | 18 | http://127.0.0.1:8081/reconnect 19 | 20 | Reconnect all sources. If channels have more than one source 21 | the source will be changed to the next available source. 22 | 23 | 24 | http://127.0.0.1:8081/reload 25 | 26 | Reload config file. This would start/stop new/removed channels. 27 | 28 | 29 | http://127.0.0.1:8081/stop/chan1 30 | 31 | Hibernates the channel "chan1". If it's already paused, 32 | it does nothing. 33 | 34 | 35 | http://127.0.0.1:8081/start/chan1 36 | 37 | If the "chan1" channel is hibernating, it restarts it. If it's 38 | already running, it does nothing. 39 | 40 | 41 | http://127.0.0.1:8081/status 42 | 43 | Return current channels status. The status looks like this: 44 | 45 | # Status DestAddr ConnTime ReadBytes ChanName ChanSource ProxyStatus 46 | CONN_OK 239.78.78.78:5000 123 148708 chan1 http://example.com:8080/stb/chan1.mpg Synced 47 | HIBERNATE 239.79.79.79:5000 0 0 chan2 http://ux.iptv.bg:8080/stb/chan2.mpg Initialized 48 | CONN_ERROR 239.80.80.80:5000 60 65800 chan3 udp://239.1.2.3:5000/ Connected 49 | 50 | "Status" - current channel status which can be CONN_OK or CONN_ERROR or HIBERNATE 51 | 52 | CONN_ERROR - Means that the source of the channel is currently 53 | not connected or bytes read are at least 1316 or 54 | ConnTime is 0 55 | 56 | "ProxyStatus" - Last connection status 57 | 58 | Initialized 59 | Connected 60 | Working 61 | Hibernated 62 | 63 | Reconnecting 64 | Hibernating 65 | 66 | Dying 67 | Forced reconnect 68 | 69 | ERROR: Can not connect to source 70 | ERROR: Can not resolve source host 71 | ERROR: Can not sync mpeg 72 | ERROR: Dns resolve timeout 73 | ERROR: Read timeout 74 | ERROR: Source returned invalid HTTP code 75 | ERROR: Source returned no-signal 76 | ERROR: Source returned unhandled error code 77 | ERROR: Timeout while syncing mpeg 78 | ERROR: Too many zero reads 79 | ERROR: fdread() timeout while syncing mpeg 80 | ERROR: fdread() timeout while syncing mpeg 81 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mptsd configuration header file 3 | * Copyright (C) 2010-2011 Unix Solutions Ltd. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 2 7 | * as published by the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. 17 | */ 18 | #ifndef CONFIG_H 19 | #define CONFIG_H 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | #include "libfuncs/libfuncs.h" 26 | 27 | typedef enum { udp_sock, tcp_sock } channel_source; 28 | 29 | typedef struct { 30 | channel_source sproto; 31 | char *proto; 32 | char *host; 33 | char *path; 34 | unsigned int port; 35 | } CHANSRC; 36 | 37 | #define MAX_CHANNEL_SOURCES 8 38 | 39 | typedef struct { 40 | char *name; 41 | char *source; /* Full source url */ 42 | char *sources[MAX_CHANNEL_SOURCES]; 43 | uint8_t num_src; 44 | uint8_t curr_src; 45 | char *dest_host; 46 | int dest_port; 47 | } CHANNEL; 48 | 49 | typedef struct { 50 | char *name; 51 | CHANNEL *channel; 52 | int sock; /* Server socket */ 53 | struct sockaddr_in src_sockname; 54 | int clientsock; /* The udp socket */ 55 | struct sockaddr_in dst_sockname; 56 | int reconnect:1, /* Set to 1 to force proxy reconnect */ 57 | connected:1, /* It's set to 1 when proxy is connected and serving clients */ 58 | dienow:1, /* Stop serving clients and exit now */ 59 | hibernate:1, /* Sleep channel (while nobody uses him) */ 60 | freechannel:1; /* Free channel data on object free (this is used in chanconf) */ 61 | int cookie; /* Used in chanconf to determine if the restreamer is alrady checked */ 62 | pthread_t thread; 63 | pthread_rwlock_t lock; 64 | time_t conn_ts; 65 | uint64_t read_bytes; 66 | int64_t last_decrypted_input_ts; 67 | char status[64]; 68 | } RESTREAMER; 69 | 70 | 71 | struct config { 72 | char *ident; 73 | char *pidfile; 74 | 75 | int syslog_active; 76 | char *logident; 77 | char *loghost; 78 | int logport; 79 | 80 | struct sockaddr_in server; 81 | char *server_addr; 82 | int server_port; 83 | int server_socket; 84 | pthread_t server_thread; 85 | int detect_encrypted_input; 86 | 87 | char *channels_file; 88 | 89 | LIST *chanconf; 90 | LIST *restreamer; 91 | 92 | pthread_mutex_t channels_lock; 93 | }; 94 | 95 | extern void do_reconnect(int sig); 96 | extern void do_reconf(int sig); 97 | extern struct config *get_config(void); 98 | 99 | #endif 100 | -------------------------------------------------------------------------------- /bitstream.h: -------------------------------------------------------------------------------- 1 | /* 2 | * TS bitstream functions 3 | * The following functions are copied from bitstream/mpeg.ts and bitstream/mpeg/pes.h 4 | 5 | * Copyright (c) 2010-2011 VideoLAN 6 | 7 | * Permission is hereby granted, free of charge, to any person obtaining 8 | * a copy of this software and associated documentation files (the 9 | * "Software"), to deal in the Software without restriction, including 10 | * without limitation the rights to use, copy, modify, merge, publish, 11 | * distribute, sublicense, and/or sell copies of the Software, and to 12 | * permit persons to whom the Software is furnished to do so, subject 13 | * to the following conditions: 14 | 15 | * The above copyright notice and this permission notice shall be 16 | * included in all copies or substantial portions of the Software. 17 | 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | */ 26 | #ifndef BITSTREAM_H 27 | #define BITSTREAM_H 28 | 29 | #include 30 | #include 31 | 32 | #define TS_SIZE 188 33 | #define TS_HEADER_SIZE 4 34 | #define PES_HEADER_SIZE_PTS 14 35 | #define PES_HEADER_SIZE_PTSDTS 19 36 | #define PES_STREAM_ID_MIN 0xbc 37 | #define PES_STREAM_ID_PRIVATE_2 0xbf 38 | 39 | static inline bool ts_validate(const uint8_t *p_ts) 40 | { 41 | return p_ts[0] == 0x47; 42 | } 43 | 44 | static inline bool ts_get_unitstart(const uint8_t *p_ts) 45 | { 46 | return !!(p_ts[1] & 0x40); 47 | } 48 | 49 | static inline bool ts_has_payload(const uint8_t *p_ts) 50 | { 51 | return !!(p_ts[3] & 0x10); 52 | } 53 | 54 | static inline bool ts_has_adaptation(const uint8_t *p_ts) 55 | { 56 | return !!(p_ts[3] & 0x20); 57 | } 58 | 59 | static inline uint8_t ts_get_adaptation(const uint8_t *p_ts) 60 | { 61 | return p_ts[4]; 62 | } 63 | 64 | static inline uint8_t pes_get_streamid(const uint8_t *p_pes) 65 | { 66 | return p_pes[3]; 67 | } 68 | 69 | static inline bool pes_validate(const uint8_t *p_pes) 70 | { 71 | return (p_pes[0] == 0x0 && p_pes[1] == 0x0 && p_pes[2] == 0x1 72 | && p_pes[3] >= PES_STREAM_ID_MIN); 73 | } 74 | 75 | static inline bool pes_validate_header(const uint8_t *p_pes) 76 | { 77 | return ((p_pes[6] & 0xc0) == 0x80); 78 | } 79 | 80 | static inline bool pes_has_pts(const uint8_t *p_pes) 81 | { 82 | return !!(p_pes[7] & 0x80); 83 | } 84 | 85 | static inline bool pes_has_dts(const uint8_t *p_pes) 86 | { 87 | return (p_pes[7] & 0xc0) == 0xc0; 88 | } 89 | 90 | static inline bool pes_validate_pts(const uint8_t *p_pes) 91 | { 92 | return ((p_pes[9] & 0xe1) == 0x21) 93 | && (p_pes[11] & 0x1) && (p_pes[13] & 0x1); 94 | } 95 | 96 | static inline bool pes_validate_dts(const uint8_t *p_pes) 97 | { 98 | return (p_pes[9] & 0x10) && ((p_pes[14] & 0xf1) == 0x11) 99 | && (p_pes[16] & 0x1) && (p_pes[18] & 0x1); 100 | } 101 | 102 | #endif 103 | -------------------------------------------------------------------------------- /web_server.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mptsd internal web server 3 | * Copyright (C) 2010-2011 Unix Solutions Ltd. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 2 7 | * as published by the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. 17 | */ 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "libfuncs/libfuncs.h" 27 | 28 | #include "web_pages.h" 29 | #include "web_server.h" 30 | 31 | typedef struct req_info { 32 | int clientsock; 33 | struct sockaddr_in client; 34 | } request_info; 35 | 36 | extern int keep_going; 37 | 38 | #define NEXT_CLIENT { FREE(path); FREE(buf); pthread_exit(0); } 39 | #define SHUTDOWN_CLIENT { FREE(path); FREE(buf); shutdown_fd(&clientsock); pthread_exit(0); } 40 | #define BUF_SIZE 1024 41 | 42 | void *process_web_request(void *); 43 | 44 | void *web_server_thread(void *data) { 45 | struct config *conf = data; 46 | while (keep_going) { 47 | struct sockaddr_in client; 48 | unsigned int clientlen = sizeof(client); 49 | int clientsock; 50 | clientsock = accept(conf->server_socket, (struct sockaddr *) &client, &clientlen); 51 | if (clientsock < 0) { 52 | if (conf->server_socket > -1) // The server_socket is closed on exit, so do not report errors 53 | LOGf("ERROR : Failed to accept client fd: %i err: %s\n", clientsock, strerror(errno)); 54 | if (errno==EMFILE || errno==ENFILE) /* No more FDs */ 55 | break; 56 | } else { 57 | request_info *req; 58 | pthread_t req_thread; 59 | req = malloc(sizeof(request_info)); 60 | if (!req) { 61 | log_perror("Can't allocate request_info", errno); 62 | continue; 63 | } 64 | req->clientsock = clientsock; 65 | req->client = client; 66 | if (pthread_create(&req_thread, NULL, (void *)&process_web_request, (void *)req)) { 67 | log_perror("Error creating request processing thread.", errno); 68 | exit(1); 69 | } 70 | pthread_detach(req_thread); 71 | } 72 | } 73 | 74 | pthread_exit(0); 75 | } 76 | 77 | void web_server_start(struct config *conf) { 78 | if (conf->server_socket > -1) 79 | pthread_create(&conf->server_thread, NULL, &web_server_thread, conf); 80 | } 81 | 82 | void web_server_stop(struct config *conf) { 83 | if (conf->server_socket > -1) { 84 | shutdown_fd(&conf->server_socket); 85 | pthread_join(conf->server_thread, NULL); 86 | } 87 | } 88 | 89 | void *process_web_request(void *in_req) { 90 | request_info *req = (request_info *)in_req; 91 | int clientsock = req->clientsock; 92 | regmatch_t res[3]; 93 | char *path=NULL, *buf=NULL; 94 | FREE(req); 95 | 96 | signal(SIGPIPE, SIG_IGN); 97 | 98 | if (!keep_going) 99 | pthread_exit(0); 100 | 101 | buf = malloc(BUF_SIZE); 102 | if (!buf) { 103 | log_perror("Can't allocate buffer", errno); 104 | SHUTDOWN_CLIENT; 105 | } 106 | 107 | if (fdgetline(clientsock,buf,BUF_SIZE)<=0) { 108 | SHUTDOWN_CLIENT; 109 | } 110 | 111 | regex_t request_get; 112 | regcomp(&request_get, "^GET /([^ ]*) HTTP/1.*$", REG_EXTENDED); 113 | if (regexec(&request_get,buf,2,res,0)==REG_NOMATCH) { 114 | send_501_not_implemented(clientsock); 115 | SHUTDOWN_CLIENT; 116 | } 117 | 118 | buf[res[1].rm_eo]=0; 119 | chomp(buf+res[1].rm_so); 120 | if (buf[res[1].rm_eo-1]=='/') buf[res[1].rm_eo-1]=0; 121 | path = strdup(buf+res[1].rm_so); 122 | regfree(&request_get); 123 | 124 | while (fdgetline(clientsock,buf,BUF_SIZE) > 0) { 125 | if (buf[0] == '\n' || buf[0] == '\r') // End of headers 126 | break; 127 | } 128 | 129 | if (strlen(path) == 0) { 130 | cmd_index(clientsock); 131 | } else if (strstr(path,"getconfig")==path) { 132 | cmd_getconfig(clientsock); 133 | } else if (strstr(path,"reconnect")==path) { 134 | cmd_reconnect(clientsock); 135 | } else if (strstr(path,"reload")==path) { 136 | cmd_reload(clientsock); 137 | } else if (strstr(path,"status")==path) { 138 | cmd_status(clientsock); 139 | } else if (strstr(path,"start")==path) { 140 | cmd_start(clientsock,path); 141 | } else if (strstr(path,"stop")==path) { 142 | cmd_stop(clientsock,path); 143 | } else { 144 | send_404_not_found(clientsock); 145 | } 146 | 147 | SHUTDOWN_CLIENT; 148 | } 149 | 150 | -------------------------------------------------------------------------------- /web_pages.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mptsd internal web pages 3 | * Copyright (C) 2010-2011 Unix Solutions Ltd. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 2 7 | * as published by the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. 17 | */ 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "libfuncs/io.h" 27 | #include "libfuncs/log.h" 28 | #include "libfuncs/list.h" 29 | #include "libfuncs/http_response.h" 30 | 31 | #include "config.h" 32 | 33 | extern struct config *config; 34 | extern int set_hibernate(int k, const char *channel); 35 | 36 | void cmd_index(int clientsock) { 37 | send_200_ok(clientsock); 38 | send_header_textplain(clientsock); 39 | fdputs(clientsock, "\nHi from tomcast.\n"); 40 | } 41 | 42 | void cmd_status(int clientsock) { 43 | send_200_ok(clientsock); 44 | send_header_textplain(clientsock); 45 | fdputs(clientsock, "\n"); 46 | 47 | LNODE *l, *tmp; 48 | struct config *cfg = get_config(); 49 | 50 | time_t now = time(NULL); 51 | fdputsf(clientsock, "%-10s %-20s %8s %10s %-18s %-64s %s\n", 52 | "# Status", 53 | "DestAddr", 54 | "ConnTime", 55 | "ReadBytes", 56 | "ChanName", 57 | "ChanSource", 58 | "ProxyStatus" 59 | ); 60 | pthread_mutex_lock(&cfg->channels_lock); 61 | list_lock(cfg->restreamer); 62 | list_for_each(cfg->restreamer, l, tmp) { 63 | char dest[32]; 64 | int conn_status = 0; 65 | RESTREAMER *r = l->data; 66 | pthread_rwlock_rdlock(&r->lock); 67 | snprintf(dest, sizeof(dest), "%s:%d", r->channel->dest_host, r->channel->dest_port); 68 | if (r->connected && r->conn_ts > 0 && r->read_bytes >= 1316 && !r->hibernate) 69 | conn_status = 1; 70 | fdputsf(clientsock, "%-10s %-20s %8lu %10llu %-18s %-64s %s\n", 71 | conn_status ? "CONN_OK" : r->hibernate ? "HIBERNATE" : "CONN_ERROR", 72 | dest, 73 | r->conn_ts ? now - r->conn_ts : 0, 74 | r->read_bytes, 75 | r->channel->name, 76 | r->channel->source, 77 | r->status 78 | ); 79 | pthread_rwlock_unlock(&r->lock); 80 | } 81 | list_unlock(cfg->restreamer); 82 | pthread_mutex_unlock(&cfg->channels_lock); 83 | } 84 | 85 | void cmd_getconfig(int clientsock) { 86 | send_200_ok(clientsock); 87 | send_header_textplain(clientsock); 88 | fdputs(clientsock, "\n"); 89 | 90 | LNODE *l, *tmp; 91 | struct config *cfg = get_config(); 92 | 93 | pthread_mutex_lock(&cfg->channels_lock); 94 | list_lock(cfg->restreamer); 95 | list_for_each(cfg->restreamer, l, tmp) { 96 | RESTREAMER *r = l->data; 97 | pthread_rwlock_rdlock(&r->lock); 98 | int i; 99 | for (i = 0; i < r->channel->num_src; i++) { 100 | fdputsf(clientsock, "%s\t%s:%d\t%s\n", 101 | r->channel->name, 102 | r->channel->dest_host, 103 | r->channel->dest_port, 104 | r->channel->sources[i] 105 | ); 106 | } 107 | pthread_rwlock_unlock(&r->lock); 108 | } 109 | list_unlock(cfg->restreamer); 110 | pthread_mutex_unlock(&cfg->channels_lock); 111 | } 112 | 113 | void cmd_reconnect(int clientsock) { 114 | send_200_ok(clientsock); 115 | send_header_textplain(clientsock); 116 | struct config *cfg = get_config(); 117 | pthread_mutex_lock(&cfg->channels_lock); 118 | fdputsf(clientsock, "\nReconnecting %d inputs.\n", cfg->chanconf->items); 119 | pthread_mutex_unlock(&cfg->channels_lock); 120 | do_reconnect(1); 121 | } 122 | 123 | void cmd_reload(int clientsock) { 124 | send_200_ok(clientsock); 125 | send_header_textplain(clientsock); 126 | fdputs(clientsock, "\nReloading config\n"); 127 | do_reconf(1); 128 | } 129 | 130 | void cmd_start(int clientsock, const char *uri) { 131 | if(uri[5] == '/') { 132 | struct config *cfg = get_config(); 133 | pthread_mutex_lock(&cfg->channels_lock); 134 | int ret = set_hibernate(0,uri+6); 135 | pthread_mutex_unlock(&cfg->channels_lock); 136 | if (ret > 0) { 137 | send_200_ok(clientsock); 138 | send_header_textplain(clientsock); 139 | fdputs(clientsock, "\nok\n"); 140 | return; 141 | } 142 | if (ret == 0) { 143 | send_403_forbidden(clientsock); 144 | return; 145 | } 146 | } 147 | send_404_not_found(clientsock); 148 | } 149 | 150 | void cmd_stop(int clientsock, const char *uri) { 151 | if(uri[4] == '/') { 152 | struct config *cfg = get_config(); 153 | pthread_mutex_lock(&cfg->channels_lock); 154 | int ret = set_hibernate(1,uri+5); 155 | pthread_mutex_unlock(&cfg->channels_lock); 156 | if (ret > 0) { 157 | send_200_ok(clientsock); 158 | send_header_textplain(clientsock); 159 | fdputs(clientsock, "\nok\n"); 160 | return; 161 | } 162 | if (ret == 0) { 163 | send_403_forbidden(clientsock); 164 | return; 165 | } 166 | } 167 | send_404_not_found(clientsock); 168 | } 169 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Library General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License 307 | along with this program; if not, write to the Free Software 308 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 309 | 310 | 311 | Also add information on how to contact you by electronic and paper mail. 312 | 313 | If the program is interactive, make it output a short notice like this 314 | when it starts in an interactive mode: 315 | 316 | Gnomovision version 69, Copyright (C) year name of author 317 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 318 | This is free software, and you are welcome to redistribute it 319 | under certain conditions; type `show c' for details. 320 | 321 | The hypothetical commands `show w' and `show c' should show the appropriate 322 | parts of the General Public License. Of course, the commands you use may 323 | be called something other than `show w' and `show c'; they could even be 324 | mouse-clicks or menu items--whatever suits your program. 325 | 326 | You should also get your employer (if you work as a programmer) or your 327 | school, if any, to sign a "copyright disclaimer" for the program, if 328 | necessary. Here is a sample; alter the names: 329 | 330 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 331 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 332 | 333 | , 1 April 1989 334 | Ty Coon, President of Vice 335 | 336 | This General Public License does not permit incorporating your program into 337 | proprietary programs. If your program is a subroutine library, you may 338 | consider it more useful to permit linking proprietary applications with the 339 | library. If this is what you want to do, use the GNU Library General 340 | Public License instead of this License. 341 | -------------------------------------------------------------------------------- /tomcast.c: -------------------------------------------------------------------------------- 1 | /* 2 | * tomcast 3 | * Copyright (C) 2010-2013 Unix Solutions Ltd. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 2 7 | * as published by the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License (COPYING file) for more details. 13 | * 14 | */ 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include // for uint32_t 33 | 34 | #include "libfuncs/libfuncs.h" 35 | #include "bitstream.h" 36 | #include "config.h" 37 | 38 | #include "web_server.h" 39 | 40 | #define DNS_RESOLVER_TIMEOUT 5000 41 | 42 | #define FDGETLINE_TIMEOUT 500 43 | #define FDGETLINE_RETRIES 30 44 | 45 | #define FDREAD_TIMEOUT 1500 46 | #define FDREAD_RETRIES 7 47 | 48 | #define FDWRITE_TIMEOUT 1500 49 | #define FDWRITE_RETRIES 7 50 | 51 | /* How much to wait for connection to be established with channel source (miliseconds) */ 52 | #define PROXY_CONNECT_TIMEOUT 1000 53 | 54 | /* Seconds to sleep between retries (miliseconds) */ 55 | #define PROXY_RETRY_TIMEOUT 1000 56 | 57 | #define TRANSPORT_PACKET_SIZE 188 58 | #define TRANSPORT_EXTEDED_PACKET_SIZE 192 59 | #define TRANSPORT_PACKETS_PER_NETWORK_PACKET 7 60 | #define TRANSPORT_SYNC_BYTE 0x47 61 | 62 | #define FRAME_PACKET_SIZE (TRANSPORT_PACKET_SIZE * TRANSPORT_PACKETS_PER_NETWORK_PACKET) 63 | 64 | #ifndef FREE 65 | #define FREE(x) if(x) { free(x); x=NULL; } 66 | #endif 67 | 68 | #ifndef POLLRDHUP 69 | #define POLLRDHUP 0 70 | #endif 71 | 72 | char *server_sig = "tomcast"; 73 | char *server_ver = "1.40"; 74 | char *copyright = "Copyright (C) 2010-2018 Unix Solutions Ltd."; 75 | 76 | static struct config config; 77 | 78 | #ifdef __MACH__ 79 | #include 80 | #include 81 | #endif 82 | 83 | int64_t get_time(void) { 84 | struct timespec ts; 85 | #ifdef __MACH__ 86 | // OS X does not have clock_gettime, use clock_get_time 87 | clock_serv_t cclock; 88 | mach_timespec_t mts; 89 | host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); 90 | clock_get_time(cclock, &mts); 91 | mach_port_deallocate(mach_task_self(), cclock); 92 | ts.tv_sec = mts.tv_sec; 93 | ts.tv_nsec = mts.tv_nsec; 94 | #else 95 | if (clock_gettime(CLOCK_MONOTONIC, &ts) == EINVAL) // Shouldn't happen on modern Linux 96 | clock_gettime(CLOCK_REALTIME, &ts); 97 | #endif 98 | return ts.tv_sec * 1000000ll + ts.tv_nsec / 1000; 99 | } 100 | 101 | channel_source get_sproto(char *url) { 102 | return strncmp(url, "http", 4)==0 ? tcp_sock : udp_sock; 103 | } 104 | 105 | CHANSRC *init_chansrc(char *url) { 106 | regex_t re; 107 | regmatch_t res[5]; 108 | regcomp(&re, "^([a-z]+)://([^:/?]+):?([0-9]*)/?(.*)", REG_EXTENDED); 109 | if (regexec(&re,url,5,res,0)==0) { 110 | char *data = strdup(url); 111 | char *proto, *host, *port, *path; 112 | int iport; 113 | proto= data+res[1].rm_so; data[res[1].rm_eo]=0; 114 | host = data+res[2].rm_so; data[res[2].rm_eo]=0; 115 | port = data+res[3].rm_so; data[res[3].rm_eo]=0; 116 | path = data+res[4].rm_so; data[res[4].rm_eo]=0; 117 | iport = atoi(port); 118 | /* Setup */ 119 | CHANSRC *src = calloc(1, sizeof(CHANSRC)); 120 | src->proto = strdup(proto); 121 | src->sproto= get_sproto(url); 122 | src->host = strdup(host); 123 | src->port = iport ? iport : 80; 124 | src->path = strdup(path); 125 | FREE(data); 126 | regfree(&re); 127 | return src; 128 | } 129 | regfree(&re); 130 | return NULL; 131 | } 132 | 133 | void free_chansrc(CHANSRC *url) { 134 | if (url) { 135 | FREE(url->proto); 136 | FREE(url->host); 137 | FREE(url->path); 138 | FREE(url); 139 | } 140 | }; 141 | 142 | int is_valid_url(char *url) { 143 | regex_t re; 144 | regmatch_t res[5]; 145 | int ret; 146 | regcomp(&re, "^([a-z]+)://([^:/?]+):?([0-9]*)/?(.*)", REG_EXTENDED); 147 | ret = regexec(&re,url,5,res,0); 148 | regfree(&re); 149 | return ret == 0; 150 | } 151 | 152 | void add_channel_source(CHANNEL *c, char *src) { 153 | if (c->num_src >= MAX_CHANNEL_SOURCES-1) 154 | return; 155 | c->sources[c->num_src] = strdup(src); 156 | if (c->num_src == 0) /* Set default source to first one */ 157 | c->source = c->sources[c->num_src]; 158 | c->num_src++; 159 | } 160 | 161 | void next_channel_source(CHANNEL *c) { 162 | if (c->num_src <= 1) 163 | return; 164 | // uint8_t old_src = c->curr_src; 165 | c->curr_src++; 166 | if (c->curr_src >= MAX_CHANNEL_SOURCES-1 || c->sources[c->curr_src] == NULL) 167 | c->curr_src = 0; 168 | c->source = c->sources[c->curr_src]; 169 | // LOGf("CHAN : Switch source | Channel: %s OldSrc: %d %s NewSrc: %d %s\n", c->name, old_src, c->sources[old_src], c->curr_src, c->source); 170 | } 171 | 172 | void set_channel_source(CHANNEL *c, uint8_t src_id) { 173 | if (src_id >= MAX_CHANNEL_SOURCES-1 || c->sources[src_id] == NULL) 174 | return; 175 | // uint8_t old_src = c->curr_src; 176 | c->curr_src = src_id; 177 | c->source = c->sources[c->curr_src]; 178 | // LOGf("CHAN : Set source | Channel: %s OldSrc: %d %s NewSrc: %d %s\n", c->name, old_src, c->sources[old_src], c->curr_src, c->source); 179 | } 180 | 181 | CHANNEL * new_channel(char *name, char *source, char *dest, int port) { 182 | CHANNEL *c = calloc(1, sizeof(CHANNEL)); 183 | c->name = strdup(name); 184 | c->dest_host = strdup(dest); 185 | c->dest_port = port; 186 | add_channel_source(c, source); 187 | return c; 188 | } 189 | 190 | void free_channel(CHANNEL *c) { 191 | int i; 192 | for (i=c->num_src-1; i>=0; i--) { 193 | FREE(c->sources[i]); 194 | } 195 | FREE(c->name); 196 | FREE(c->dest_host); 197 | c->source = NULL; 198 | FREE(c); 199 | } 200 | 201 | void free_channel_p(void *c) { 202 | free_channel(c); 203 | } 204 | 205 | int send_reset_opt = 0; 206 | int multicast_ttl = 1; 207 | int init_chan_stop_opt = 0; 208 | struct in_addr output_intf = { .s_addr = INADDR_ANY }; 209 | 210 | int connect_multicast(struct sockaddr_in send_to) { 211 | int sendsock = socket(AF_INET, SOCK_DGRAM, 0); 212 | if (sendsock < 0) { 213 | LOGf("socket(SOCK_DGRAM): %s\n", strerror(errno)); 214 | return -1; 215 | } 216 | int on = 1; 217 | setsockopt(sendsock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); 218 | // subscribe to multicast group 219 | // LOGf("Using ttl %d\n", multicast_ttl); 220 | if (IN_MULTICAST(ntohl(send_to.sin_addr.s_addr))) { 221 | if (setsockopt(sendsock, IPPROTO_IP, IP_MULTICAST_TTL, &multicast_ttl, sizeof(multicast_ttl)) < 0) { 222 | LOGf("setsockopt(IP_MUTICAST_TTL): %s\n", strerror(errno)); 223 | close(sendsock); 224 | return -1; 225 | } 226 | if (setsockopt(sendsock, IPPROTO_IP, IP_MULTICAST_IF, &output_intf, sizeof(output_intf)) < 0) { 227 | LOGf("setsockopt(IP_MUTICAST_IF, %s): %s\n", strerror(errno), inet_ntoa(output_intf)); 228 | close(sendsock); 229 | return -1; 230 | } 231 | } 232 | 233 | int writebuflen = FRAME_PACKET_SIZE * 1000; 234 | if (setsockopt(sendsock, SOL_SOCKET, SO_SNDBUF, (const char *)&writebuflen, sizeof(writebuflen)) < 0) 235 | log_perror("setsockopt(): setsockopt(SO_SNDBUF)", errno); 236 | 237 | // call connect to get errors 238 | if (connect(sendsock, (struct sockaddr *)&send_to, sizeof send_to)) { 239 | LOGf("udp_connect() error: %s\n", strerror(errno)); 240 | close(sendsock); 241 | return -1; 242 | } 243 | return sendsock; 244 | } 245 | 246 | void proxy_set_status(RESTREAMER *r, const char *proxy_status) { 247 | pthread_rwlock_wrlock(&r->lock); 248 | snprintf(r->status, sizeof(r->status), "%s", proxy_status); 249 | pthread_rwlock_unlock(&r->lock); 250 | } 251 | 252 | void connect_destination(RESTREAMER *r) { 253 | CHANNEL *c = r->channel; 254 | if (r->clientsock >= 0) 255 | shutdown_fd(&(r->clientsock)); 256 | r->clientsock = connect_multicast(r->dst_sockname); 257 | LOGf("CONN : Connected dst_fd: %i | Chan: %s Dest: udp://%s:%d\n", r->clientsock, c->name, c->dest_host, c->dest_port); 258 | } 259 | 260 | RESTREAMER * new_restreamer(const char *name, CHANNEL *channel) { 261 | int active = 1; 262 | struct sockaddr_in sockname; 263 | int dret = async_resolve_host(channel->dest_host, channel->dest_port, &sockname, DNS_RESOLVER_TIMEOUT, &active); 264 | if (dret != 0) { 265 | if (dret == 1) 266 | LOGf("ERR : Can't resolve host | Chan: %s Dest: udp://%s:%d\n", channel->name, channel->dest_host, channel->dest_port); 267 | if (dret == 2) 268 | LOGf("ERR : DNS timeout | Chan: %s Dest: udp://%s:%d\n", channel->name, channel->dest_host, channel->dest_port); 269 | return NULL; 270 | } 271 | RESTREAMER *r = calloc(1, sizeof(RESTREAMER)); 272 | r->name = strdup(name); 273 | r->sock = -1; 274 | r->channel = channel; 275 | r->clientsock = -1; 276 | r->dst_sockname = sockname; 277 | r->hibernate = init_chan_stop_opt; 278 | pthread_rwlock_init(&r->lock, NULL); 279 | connect_destination(r); 280 | r->last_decrypted_input_ts = get_time(); 281 | return r; 282 | } 283 | 284 | void free_restreamer(RESTREAMER *r) { 285 | if (r->sock > -1) 286 | shutdown_fd(&(r->sock)); 287 | if (r->freechannel) 288 | free_channel(r->channel); 289 | FREE(r->name); 290 | FREE(r); 291 | } 292 | 293 | char TS_NULL_FRAME[FRAME_PACKET_SIZE]; 294 | 295 | regex_t http_response; 296 | 297 | void proxy_log(RESTREAMER *r, char *msg, char *info) { 298 | LOGf("%s: %s Chan: %s Src: %s Dst: udp://%s:%d SrcIP: %s SrcFD: %i DstFD: %i\n", 299 | msg, 300 | info, 301 | r->channel->name, 302 | r->channel->source, 303 | r->channel->dest_host, 304 | r->channel->dest_port, 305 | inet_ntoa(r->src_sockname.sin_addr), 306 | r->sock, 307 | r->clientsock 308 | ); 309 | } 310 | 311 | int load_channels_config(struct config *cfg) { 312 | regex_t re; 313 | regmatch_t res[5]; 314 | char line[1024]; 315 | int fd; 316 | int num_channels = 0; 317 | 318 | if (pthread_mutex_trylock(&cfg->channels_lock) != 0) 319 | return -1; 320 | 321 | fd = open(cfg->channels_file, O_RDONLY); 322 | 323 | if (fd != -1) { 324 | struct timeval tv; 325 | gettimeofday(&tv, NULL); 326 | unsigned int randstate = tv.tv_usec; 327 | int cookie = rand_r(&randstate); 328 | 329 | regcomp(&re, "^([A-Za-z0-9]+)\t+([0-9.]+):([0-9]+)\t+(.*)", REG_EXTENDED); 330 | LIST *old_chanconf; 331 | LIST *new_chanconf = list_new("chanconf"); 332 | while (fdgetline(fd,line,sizeof(line)) > 0) { 333 | chomp(line); 334 | if (regexec(&re,line,5,res,0)==0) { 335 | char *name, *dest_host, *dest_port, *source; 336 | char *org = strdup(line); 337 | name = org+res[1].rm_so; org[res[1].rm_eo]=0; 338 | dest_host = org+res[2].rm_so; org[res[2].rm_eo]=0; 339 | dest_port = org+res[3].rm_so; org[res[3].rm_eo]=0; 340 | source = org+res[4].rm_so; org[res[4].rm_eo]=0; 341 | 342 | if (!is_valid_url(source)) { 343 | LOGf("CONF : Invalid url: %s\n", source); 344 | FREE(org); 345 | goto report_error; 346 | } 347 | /* Search for already added channel */ 348 | LNODE *l, *tmp; 349 | CHANNEL *chan = NULL; 350 | list_for_each_reverse(new_chanconf, l, tmp) { 351 | if (strcmp(name, ((CHANNEL *)l->data)->name)==0) { 352 | chan = l->data; 353 | break; 354 | } 355 | } 356 | if (!chan) { 357 | list_add(new_chanconf, new_channel(name, source, dest_host, atoi(dest_port))); 358 | num_channels++; 359 | } else { 360 | add_channel_source(chan, source); 361 | } 362 | FREE(org); 363 | } else { 364 | report_error: 365 | if (strlen(line) > 2 && line[0] != '#') { 366 | LOGf("CONF : Invalid config line: %s\n", line); 367 | } 368 | } 369 | } 370 | regfree(&re); 371 | shutdown_fd(&fd); 372 | /* Save current chanconf */ 373 | old_chanconf = cfg->chanconf; 374 | /* Switch chanconf */ 375 | cfg->chanconf = new_chanconf; 376 | /* Rewrite restreamer channels */ 377 | LNODE *lc, *lr, *lctmp, *lrtmp; 378 | CHANNEL *chan; 379 | list_lock(cfg->restreamer); // Unlocked after second list_for_each(restreamer) 380 | 381 | list_lock(cfg->chanconf); 382 | list_for_each(cfg->chanconf, lc, lctmp) { 383 | chan = lc->data; 384 | list_for_each(cfg->restreamer, lr, lrtmp) { 385 | if (strcmp(chan->name, ((RESTREAMER *)lr->data)->name)==0) { 386 | RESTREAMER *restr = lr->data; 387 | /* Mark the restreamer as valid */ 388 | restr->cookie = cookie; 389 | /* Check if current source exists in new channel configuration */ 390 | int i, src_found = -1; 391 | char *old_source = restr->channel->source; 392 | for (i=0; inum_src; i++) { 393 | if (strcmp(old_source, chan->sources[i]) == 0) { 394 | src_found = i; 395 | } 396 | } 397 | if (src_found > -1) { 398 | /* New configuration contains existing source, just update the reference */ 399 | set_channel_source(chan, src_found); 400 | restr->channel = chan; 401 | } else { 402 | /* New configuration *DO NOT* contain existing source. Force reconnect */ 403 | LOGf("PROXY: Source changed | Channel: %s srv_fd: %d Old:%s New:%s\n", chan->name, restr->sock, restr->channel->source, chan->source); 404 | /* The order is important! */ 405 | set_channel_source(chan, chan->num_src-1); /* Set source to last one. On reconnect next source will be used. */ 406 | restr->channel = chan; 407 | restr->reconnect = 1; 408 | } 409 | break; 410 | } 411 | } 412 | } 413 | list_unlock(cfg->chanconf); 414 | 415 | /* Kill restreamers that serve channels that no longer exist */ 416 | list_for_each(cfg->restreamer, lr, lrtmp) { 417 | RESTREAMER *r = lr->data; 418 | /* This restreamer should no longer serve clients */ 419 | if (r->cookie != cookie) { 420 | proxy_log(r, "CLEAR", "Channel removed "); 421 | /* Replace channel reference with real object and instruct free_restreamer to free it */ 422 | r->channel = new_channel(r->channel->name, r->channel->source, r->channel->dest_host, r->channel->dest_port); 423 | r->freechannel = 1; 424 | r->dienow = 1; 425 | } 426 | } 427 | list_unlock(cfg->restreamer); 428 | 429 | /* Free old_chanconf */ 430 | list_free(&old_chanconf, free_channel_p, NULL); 431 | } else { 432 | num_channels = -1; 433 | } 434 | pthread_mutex_unlock(&cfg->channels_lock); 435 | if (num_channels == -1) 436 | LOGf("CONF : Error loading channels!\n"); 437 | else 438 | LOGf("CONF : %d channels loaded\n", num_channels); 439 | return num_channels; 440 | } 441 | 442 | void proxy_close(RESTREAMER *r) { 443 | proxy_log(r, "STOP ","-"); 444 | // If there are no clients left, no "Timeout" messages will be logged 445 | list_del_entry(config.restreamer, r); 446 | free_restreamer(r); 447 | } 448 | 449 | /* 450 | On the last try, send no-signal to clients and exit 451 | otherwise wait a little bit before trying again 452 | */ 453 | #define DO_RECONNECT do \ 454 | { \ 455 | free_chansrc(src); \ 456 | if (retries == 0) { \ 457 | return -1; \ 458 | } else { \ 459 | if (errno != EHOSTUNREACH) /* When host is unreachable there is already a delay of ~4 secs per try so no sleep is needed */ \ 460 | usleep(PROXY_RETRY_TIMEOUT * 1000); \ 461 | return 1; \ 462 | } \ 463 | } while(0) 464 | 465 | #define FATAL_ERROR do \ 466 | { \ 467 | free_chansrc(src); \ 468 | return -1; \ 469 | } while (0) 470 | 471 | /* 472 | Returns: 473 | -1 = exit thread 474 | 1 = retry 475 | 0 = connected ok 476 | */ 477 | int connect_source(RESTREAMER *r, int retries, int readbuflen, int *http_code, char *url, int depth) { 478 | CHANSRC *src = init_chansrc(url); 479 | if (depth > 4) { 480 | LOGf("ERR : Redirect loop detected, depth: %d | Channel: %s Source: %s\n", depth, r->channel->name, url); 481 | FATAL_ERROR; 482 | } 483 | if (!src) { 484 | LOGf("ERR : Can't parse channel source | Channel: %s Source: %s\n", r->channel->name, r->channel->source); 485 | FATAL_ERROR; 486 | } 487 | r->connected = 0; 488 | r->reconnect = 0; 489 | 490 | int active = 1; 491 | int dret = async_resolve_host(src->host, src->port, &(r->src_sockname), DNS_RESOLVER_TIMEOUT, &active); 492 | if (dret != 0) { 493 | if (dret == 1) { 494 | proxy_log(r, "ERR ","Can't resolve src host"); 495 | proxy_set_status(r, "ERROR: Can not resolve source host"); 496 | } 497 | if (dret == 2) { 498 | proxy_log(r, "ERR ","Timeout resolving src host"); 499 | proxy_set_status(r, "ERROR: Dns resolve timeout"); 500 | } 501 | DO_RECONNECT; 502 | } 503 | 504 | char buf[1024]; 505 | *http_code = 0; 506 | if (src->sproto == tcp_sock) { 507 | r->sock = socket(PF_INET, SOCK_STREAM, 0); 508 | if (r->sock < 0) { 509 | log_perror("play(): Could not create SOCK_STREAM socket.", errno); 510 | FATAL_ERROR; 511 | } 512 | if (depth == 0) { 513 | proxy_log(r, "NEW ", "-"); 514 | } else { 515 | proxy_log(r, "NEW ", url); 516 | } 517 | if (do_connect(r->sock, (struct sockaddr *)&(r->src_sockname), sizeof(r->src_sockname), PROXY_CONNECT_TIMEOUT) < 0) { 518 | LOGf("ERR : Error connecting to %s srv_fd: %i err: %s\n", r->channel->source, r->sock, strerror(errno)); 519 | proxy_set_status(r, "ERROR: Can not connect to source"); 520 | DO_RECONNECT; 521 | } 522 | 523 | snprintf(buf,sizeof(buf)-1, "GET /%s HTTP/1.0\r\nHost: %s:%u\r\nX-Smart-Client: yes\r\nUser-Agent: %s %s (%s)\r\n\r\n", 524 | src->path, src->host, src->port, server_sig, server_ver, config.ident); 525 | buf[sizeof(buf)-1] = 0; 526 | fdwrite(r->sock, buf, strlen(buf)); 527 | 528 | char redirect_to[1024]; 529 | memset(redirect_to, 0, sizeof(redirect_to)); 530 | char xresponse[128]; 531 | memset(xresponse, 0, sizeof(xresponse)); 532 | memset(buf, 0, sizeof(buf)); 533 | regmatch_t res[4]; 534 | while (fdgetline(r->sock,buf,sizeof(buf)-1)) { 535 | if (buf[0] == '\n' || buf[0] == '\r') 536 | break; 537 | if (strstr(buf,"HTTP/1.") != NULL) { 538 | if (regexec(&http_response,buf,3,res,0) != REG_NOMATCH) { 539 | char codestr[4]; 540 | if ((unsigned long)(res[1].rm_eo - res[1].rm_so) < sizeof(xresponse)) { 541 | strncpy(xresponse, &buf[res[1].rm_so], res[1].rm_eo-res[1].rm_so); 542 | xresponse[res[1].rm_eo-res[1].rm_so] = '\0'; 543 | chomp(xresponse); 544 | strncpy(codestr, &buf[res[2].rm_so], res[2].rm_eo-res[2].rm_so); 545 | codestr[3] = 0; 546 | *http_code = atoi(codestr); 547 | } 548 | } 549 | } 550 | if (*http_code == 504) { // Extract extra error code 551 | if (strstr(buf, "X-ErrorCode: ") != NULL) { 552 | *http_code = atoi(buf+13); 553 | break; 554 | } 555 | } 556 | if (strstr(buf, "Location: ") == buf) { 557 | // Remove new line 558 | char *new_line = strchr(buf, '\r'); 559 | if (new_line) *new_line = '\0'; 560 | new_line = strchr(buf, '\n'); 561 | if (new_line) *new_line = '\0'; 562 | snprintf(redirect_to, sizeof(redirect_to)-1, "%s", buf + 10); 563 | if (strstr(redirect_to, "http://") != redirect_to) { 564 | // Assume that the redirect is relative, add proto, host and port 565 | snprintf(redirect_to, sizeof(redirect_to)-1, "http://%s:%d%s", 566 | src->host, src->port, buf + 10); 567 | LOGf("DEBUG: Converted relative location to: %s | srv_fd: %i\n", redirect_to, r->sock); 568 | } 569 | } 570 | } 571 | if (*http_code == 0) { // No valid HTTP response, retry 572 | LOGf("DEBUG: Server returned not valid HTTP code | srv_fd: %i\n", r->sock); 573 | proxy_set_status(r, "ERROR: Source returned invalid HTTP code"); 574 | DO_RECONNECT; 575 | } 576 | if (*http_code == 504) { // No signal, exit 577 | LOGf("ERR : Get no-signal for %s from %s on srv_fd: %i\n", r->channel->name, r->channel->source, r->sock); 578 | proxy_set_status(r, "ERROR: Source returned no-signal"); 579 | FATAL_ERROR; 580 | } 581 | if (*http_code == 301 || *http_code == 302) { // Handle redirects 582 | if (redirect_to[0]) { 583 | LOGf("REDIR: Get code %i for %s from %s on srv_fd: %i, handling redirect to: %s\n", *http_code, r->channel->name, r->channel->source, r->sock, redirect_to); 584 | free_chansrc(src); 585 | shutdown_fd(&(r->sock)); 586 | return connect_source(r, retries, readbuflen, http_code, redirect_to, ++depth); 587 | } 588 | // Redirect connect is OK, continue 589 | } else if (*http_code > 300) { // Unhandled or error codes, exit 590 | LOGf("ERR : Get code %i for %s from %s on srv_fd: %i exiting.\n", *http_code, r->channel->name, r->channel->source, r->sock); 591 | proxy_set_status(r, "ERROR: Source returned unhandled error code"); 592 | FATAL_ERROR; 593 | } 594 | // connected ok, continue 595 | } else { 596 | if (!IN_MULTICAST(ntohl(r->src_sockname.sin_addr.s_addr))) { 597 | LOGf("ERR : %s is not multicast address\n", r->channel->source); 598 | FATAL_ERROR; 599 | } 600 | struct ip_mreq mreq; 601 | struct sockaddr_in receiving_from; 602 | 603 | r->sock = socket(PF_INET, SOCK_DGRAM, 0); 604 | if (r->sock < 0) { 605 | log_perror("play(): Could not create SOCK_DGRAM socket.", errno); 606 | FATAL_ERROR; 607 | } 608 | LOGf("CONN : Listening on multicast socket %s srv_fd: %i retries left: %i\n", r->channel->source, r->sock, retries); 609 | int on = 1; 610 | setsockopt(r->sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); 611 | // subscribe to multicast group 612 | memcpy(&mreq.imr_multiaddr, &(r->src_sockname.sin_addr), sizeof(struct in_addr)); 613 | mreq.imr_interface.s_addr = htonl(INADDR_ANY); 614 | if (setsockopt(r->sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) { 615 | LOGf("ERR : Failed to add IP membership on %s srv_fd: %i\n", r->channel->source, r->sock); 616 | FATAL_ERROR; 617 | } 618 | // bind to the socket so data can be read 619 | memset(&receiving_from, 0, sizeof(receiving_from)); 620 | receiving_from.sin_family = AF_INET; 621 | receiving_from.sin_addr = r->src_sockname.sin_addr; 622 | receiving_from.sin_port = htons(src->port); 623 | if (bind(r->sock, (struct sockaddr *) &receiving_from, sizeof(receiving_from)) < 0) { 624 | LOGf("ERR : Failed to bind to %s srv_fd: %i\n", r->channel->source, r->sock); 625 | FATAL_ERROR; 626 | } 627 | } 628 | 629 | if (setsockopt(r->sock, SOL_SOCKET, SO_RCVBUF, (const char *)&readbuflen, sizeof(readbuflen)) < 0) 630 | log_perror("play(): setsockopt(SO_RCVBUF)", errno); 631 | 632 | proxy_set_status(r, "Connected"); 633 | r->connected = 1; 634 | 635 | free_chansrc(src); 636 | return 0; 637 | } 638 | 639 | int check_restreamer_state(RESTREAMER *r) { 640 | if (r->dienow) { 641 | // LOGf("PROXY: Forced disconnect on srv_fd: %i | Channel: %s Source: %s\n", r->sock, r->channel->name, r->channel->source); 642 | proxy_set_status(r, "Dying"); 643 | return 2; 644 | } 645 | if (r->hibernate) { 646 | proxy_set_status(r, "Hibernating"); 647 | return 3; 648 | } 649 | if (r->reconnect) { 650 | LOGf("PROXY: Forced reconnect on srv_fd: %i | Channel: %s Source: %s\n", r->sock, r->channel->name, r->channel->source); 651 | proxy_set_status(r, "Forced reconnect"); 652 | return 1; 653 | } 654 | return 0; 655 | } 656 | 657 | /* 658 | Returns: 659 | -1 = channel not found 660 | 0 = channel not changed the hibernation status 661 | N = channel updated 662 | */ 663 | int set_hibernate(int k, const char *channel) { 664 | int found = -1; 665 | int i = 0; 666 | LNODE *lr, *lrtmp; 667 | list_lock(config.restreamer); 668 | list_for_each(config.restreamer, lr, lrtmp) 669 | { 670 | RESTREAMER *r = lr->data; 671 | i++; 672 | if(strcmp(r->channel->name,channel) == 0) 673 | { 674 | found = i; 675 | if (!r->hibernate && k == 1) { 676 | r->hibernate = 1; 677 | proxy_set_status(r, "Hibernating"); 678 | break; 679 | } 680 | if (r->hibernate && k == 0) { 681 | r->hibernate = 0; 682 | r->reconnect = 1; 683 | LOGf("DEBUG: restarting from hibernation | Channel: %s\n", r->channel->name); 684 | proxy_set_status(r, "Reconnecting"); 685 | break; 686 | } 687 | found = 0; 688 | break; 689 | } 690 | } 691 | list_unlock(config.restreamer); 692 | //LOGf("DEBUG: Call to set_hibernate(%i,%s) and return: %i\n", k, channel, found); 693 | return found; 694 | } 695 | 696 | #define MAX_ZERO_READS 3 697 | 698 | /* Start: 3 seconds on connect */ 699 | /* In connection: Max UDP timeout == 3 seconds (read) + 2 seconds (connect) == 5 seconds */ 700 | #define UDP_READ_RETRIES 3 701 | #define UDP_READ_TIMEOUT 1000 702 | 703 | /* Start: 1/4 seconds on connect */ 704 | /* In connection: Max TCP timeout == 5 seconds (read) + 2 seconds (connect) == 7 seconds */ 705 | /* In connection: Max TCP timeout == 5 seconds (read) + 8 seconds (connect, host unrch) == 13 seconds */ 706 | #define TCP_READ_RETRIES 5 707 | #define TCP_READ_TIMEOUT 1000 708 | 709 | /* 710 | Returns: 711 | 0 = synced ok 712 | 1 = not synced, reconnect 713 | */ 714 | int mpeg_sync(RESTREAMER *r, int proxysock, char *channel, channel_source source_proto) { 715 | time_t sync_start = time(NULL); 716 | unsigned int sync_packets = 0; 717 | unsigned int read_bytes = 0; 718 | char syncframe[188]; 719 | 720 | int _timeout = TCP_READ_TIMEOUT; 721 | int _retries = TCP_READ_RETRIES; 722 | if (source_proto == udp_sock) { 723 | _timeout = UDP_READ_TIMEOUT; 724 | _retries = UDP_READ_RETRIES; 725 | } 726 | do { 727 | resync: 728 | if (fdread_ex(proxysock, syncframe, 1, _timeout, _retries, 1) != 1) { 729 | LOGf("DEBUG: mpeg_sync fdread() timeout | Channel: %s\n", channel); 730 | proxy_set_status(r, "ERROR: fdread() timeout while syncing mpeg"); 731 | return 1; // reconnect 732 | } 733 | // LOGf("DEBUG: Read 0x%02x Offset %u Sync: %u\n", (uint8_t)syncframe[0], read_bytes, sync_packets); 734 | read_bytes++; 735 | if (syncframe[0] == 0x47) { 736 | ssize_t rdsz = fdread_ex(proxysock, syncframe, 188-1, _timeout, _retries, 1); 737 | if (rdsz != 188-1) { 738 | LOGf("DEBUG: mpeg_sync fdread() timeout | Channel: %s\n", channel); 739 | proxy_set_status(r, "ERROR: fdread() timeout while syncing mpeg"); 740 | return 1; // reconnect 741 | } 742 | read_bytes += 188-1; 743 | if (++sync_packets == 7) // sync 7 packets 744 | break; 745 | goto resync; 746 | } else { 747 | sync_packets = 0; 748 | } 749 | if (read_bytes > FRAME_PACKET_SIZE) { // Can't sync in 1316 bytes 750 | LOGf("DEBUG: Can't sync after %d bytes | Channel: %s\n", FRAME_PACKET_SIZE, channel); 751 | proxy_set_status(r, "ERROR: Can not sync mpeg"); 752 | return 1; // reconnect 753 | } 754 | if (sync_start+2 <= time(NULL)) { // Do not sync in two seconds 755 | LOGf("DEBUG: Timeout while syncing (read %u bytes) | Channel: %s\n", read_bytes, channel); 756 | proxy_set_status(r, "ERROR: Timeout while syncing mpeg"); 757 | return 1; // reconnect 758 | } 759 | } while (1); 760 | pthread_rwlock_wrlock(&r->lock); 761 | r->conn_ts = time(NULL); 762 | r->read_bytes = read_bytes; 763 | pthread_rwlock_unlock(&r->lock); 764 | LOGf("SYNC : TS synced after %u bytes | Channel: %s\n", read_bytes-FRAME_PACKET_SIZE, channel); 765 | proxy_set_status(r, "Working"); 766 | return 0; 767 | } 768 | 769 | char reset[FRAME_PACKET_SIZE] = { 770 | 0x47,0x40,0x00,0x10,0x00,0x00,0xB0,0x09,0x27,0x10,0xC1,0x00, 771 | 0x00,0x3C,0xDD,0xFF,0xB8,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 772 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 773 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 774 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 775 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 776 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 777 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 778 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 779 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 780 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 781 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 782 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 783 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 784 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 785 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x47,0x40,0x00,0x11, 786 | 0x00,0x00,0xB0,0x0D,0x27,0x10,0xC3,0x00,0x00,0x4E,0x20,0xE0, 787 | 0x64,0xD8,0x46,0x8F,0xCB,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 788 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 789 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 790 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 791 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 792 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 793 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 794 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 795 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 796 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 797 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 798 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 799 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 800 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 801 | 0xFF,0xFF,0xFF,0xFF,0x47,0x40,0x11,0x12,0x00,0x42,0xF0,0x0C, 802 | 0x27,0x10,0xC1,0x00,0x00,0x9C,0x40,0xFF,0x1F,0xA4,0x9D,0xBA, 803 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 804 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 805 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 806 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 807 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 808 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 809 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 810 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 811 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 812 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 813 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 814 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 815 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 816 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 817 | 0x47,0x40,0x11,0x13,0x00,0x42,0xF0,0x0C,0x27,0x10,0xC3,0x00, 818 | 0x00,0x9C,0x40,0xFF,0x29,0xF4,0x87,0x4A,0xFF,0xFF,0xFF,0xFF, 819 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 820 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 821 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 822 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 823 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 824 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 825 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 826 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 827 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 828 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 829 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 830 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 831 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 832 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x47,0x40,0x64,0x14, 833 | 0x00,0x02,0xB0,0x0D,0x4E,0x20,0xC1,0x00,0x00,0xE0,0x6E,0xF0, 834 | 0x00,0x30,0xB6,0x9F,0x1A,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 835 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 836 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 837 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 838 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 839 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 840 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 841 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 842 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 843 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 844 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 845 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 846 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 847 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 848 | 0xFF,0xFF,0xFF,0xFF,0x47,0x40,0x64,0x15,0x00,0x02,0xB0,0x0D, 849 | 0x4E,0x20,0xC3,0x00,0x00,0xE0,0x78,0xF0,0x00,0xB7,0x41,0x6C, 850 | 0x5A,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 851 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 852 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 853 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 854 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 855 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 856 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 857 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 858 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 859 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 860 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 861 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 862 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 863 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 864 | 0x47,0x1F,0xFF,0x10,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 865 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 866 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 867 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 868 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 869 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 870 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 871 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 872 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 873 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 874 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 875 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 876 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 877 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 878 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 879 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF 880 | }; 881 | 882 | 883 | /* 884 | Return value: 885 | ret == 0 - No valid payload was found 886 | ret & 0x01 == 0x01 - PES was found 887 | ret & 0x02 == 0x02 - PTS was found 888 | ret & 0x04 == 0x04 - DTS was found 889 | */ 890 | static unsigned int ts_have_valid_pes(uint8_t *buf, unsigned int buffer_size) { 891 | unsigned int ret = 0; 892 | uint8_t *buf_end = buf + buffer_size; 893 | while (buf < buf_end && ts_validate(buf)) { 894 | uint16_t header_size = TS_HEADER_SIZE + (ts_has_adaptation(buf) ? 1 : 0) + ts_get_adaptation(buf); 895 | if (ts_get_unitstart(buf) && ts_has_payload(buf) && header_size + PES_HEADER_SIZE_PTS <= TS_SIZE) { 896 | //printf("Got payload\n"); 897 | if (pes_validate(buf + header_size) && pes_get_streamid(buf + header_size) != PES_STREAM_ID_PRIVATE_2 && pes_validate_header(buf + header_size)) { 898 | //printf("Got PES\n"); 899 | ret |= 0x01; 900 | if (pes_has_pts(buf + header_size) && pes_validate_pts(buf + header_size)) { 901 | ret |= 0x02; 902 | //printf("Got PTS\n"); 903 | if (header_size + PES_HEADER_SIZE_PTSDTS <= TS_SIZE && pes_has_dts(buf + header_size) && pes_validate_dts(buf + header_size)) { 904 | //printf("Got DTS\n"); 905 | ret |= 0x04; 906 | } 907 | } 908 | } 909 | } 910 | buf += TS_SIZE; 911 | } 912 | return ret; 913 | } 914 | 915 | void * proxy_ts_stream(void *self) { 916 | RESTREAMER *r = self; 917 | char buf[FRAME_PACKET_SIZE]; 918 | 919 | signal(SIGPIPE, SIG_IGN); 920 | 921 | proxy_set_status(r, "Initialized"); 922 | int http_code = 0; 923 | while (1) { 924 | r->conn_ts = 0; 925 | r->read_bytes = 0; 926 | if (r->hibernate) { 927 | sleep(1); 928 | continue; 929 | } 930 | 931 | int result = connect_source(self, 1, FRAME_PACKET_SIZE * 1000, &http_code, r->channel->source, 0); 932 | if (result > 0) 933 | goto RECONNECT; 934 | 935 | channel_source sproto = get_sproto(r->channel->source); 936 | 937 | int mpgsync = mpeg_sync(r, r->sock, r->channel->name, sproto); 938 | if (mpgsync == 1) // Timeout 939 | goto RECONNECT; 940 | 941 | ssize_t readen, written; 942 | int max_zero_reads = MAX_ZERO_READS; 943 | int send_reset = send_reset_opt; 944 | for (;;) { 945 | switch (check_restreamer_state(r)) { 946 | case 1: goto RECONNECT; // r->reconnect is on 947 | case 2: goto QUIT; // r->dienow is on 948 | case 3: goto HIBERNATE; // r->hibernate is on 949 | } 950 | if (sproto == tcp_sock) { 951 | readen = fdread_ex(r->sock, buf, FRAME_PACKET_SIZE, TCP_READ_TIMEOUT, TCP_READ_RETRIES, 1); 952 | } else { 953 | readen = fdread_ex(r->sock, buf, FRAME_PACKET_SIZE, UDP_READ_TIMEOUT, UDP_READ_RETRIES, 0); 954 | } 955 | if (readen < 0) 956 | goto RECONNECT; 957 | if (readen == 0) { // ho, hum, wtf is going on here? 958 | LOGf("PROXY: zero read on srv_fd: %i | Channel: %s Source: %s\n", r->sock, r->channel->name, r->channel->source); 959 | if (--max_zero_reads == 0) { 960 | LOGf("PROXY: %d zero reads on srv_fd: %i | Channel: %s Source: %s\n", MAX_ZERO_READS, r->sock, r->channel->name, r->channel->source); 961 | proxy_set_status(r, "ERROR: Too many zero reads"); 962 | break; 963 | } 964 | continue; 965 | } 966 | 967 | max_zero_reads = MAX_ZERO_READS; 968 | 969 | // Fill short frame with NULL packets 970 | if (readen < FRAME_PACKET_SIZE) { 971 | //LOGf("DEBUG: Short read (%d) on retreamer srv_fd: %i | Channel: %s\n", readen, sock, chan->name); 972 | memcpy(buf+readen, TS_NULL_FRAME+readen, FRAME_PACKET_SIZE - readen); 973 | } 974 | pthread_rwlock_wrlock(&r->lock); 975 | r->read_bytes += readen; 976 | pthread_rwlock_unlock(&r->lock); 977 | 978 | if (send_reset) { 979 | send_reset = 0; 980 | fdwrite(r->clientsock, reset, FRAME_PACKET_SIZE); 981 | } 982 | 983 | if (config.detect_encrypted_input) { 984 | int64_t now = get_time(); 985 | int ret; 986 | if ((ret = ts_have_valid_pes((uint8_t *)buf, readen)) == 0) { // Is the output encrypted? 987 | /* The output is encrypted, check if 1000 ms have passed and if such, notify that we probably have invalid key */ 988 | if (now > r->last_decrypted_input_ts + 500000) { 989 | proxy_log(r, "ERR ","Scrambled input"); 990 | proxy_set_status(r, "ERROR: Encrypted stream input"); 991 | goto RECONNECT; 992 | } 993 | } else { 994 | r->last_decrypted_input_ts = now; 995 | } 996 | } 997 | 998 | written = fdwrite(r->clientsock, buf, FRAME_PACKET_SIZE); 999 | if (written == -1) { 1000 | LOGf("PROXY: Error writing to dst_fd: %i on srv_fd: %i | Channel: %s Source: %s\n", r->clientsock, r->sock, r->channel->name, r->channel->source); 1001 | connect_destination(r); 1002 | } 1003 | } 1004 | LOGf("DEBUG: fdread timeout restreamer srv_fd: %i | Channel: %s\n", r->sock, r->channel->name); 1005 | proxy_set_status(r, "ERROR: Read timeout"); 1006 | RECONNECT: 1007 | pthread_rwlock_wrlock(&r->lock); 1008 | r->conn_ts = 0; 1009 | pthread_rwlock_unlock(&r->lock); 1010 | LOGf("DEBUG: reconnect srv_fd: %i | Channel: %s\n", r->sock, r->channel->name); 1011 | proxy_set_status(r, "Reconnecting"); 1012 | shutdown_fd(&(r->sock)); 1013 | next_channel_source(r->channel); 1014 | continue; 1015 | HIBERNATE: 1016 | pthread_rwlock_wrlock(&r->lock); 1017 | r->conn_ts = 0; 1018 | pthread_rwlock_unlock(&r->lock); 1019 | LOGf("DEBUG: hibernation stop srv_fd: %i | Channel: %s\n", r->sock, r->channel->name); 1020 | proxy_set_status(r, "Hibernated"); 1021 | shutdown_fd(&(r->sock)); 1022 | continue; 1023 | QUIT: 1024 | LOGf("DEBUG: quit srv_fd: %i | Channel: %s\n", r->sock, r->channel->name); 1025 | break; 1026 | } 1027 | proxy_close(r); 1028 | return 0; 1029 | } 1030 | 1031 | static int copyright_shown = 0; 1032 | void show_usage(int ident_only) { 1033 | if (!copyright_shown) { 1034 | printf("%s %s\n", server_sig, server_ver); 1035 | puts(copyright); 1036 | puts(""); 1037 | copyright_shown = 1; 1038 | } 1039 | if (ident_only) 1040 | return; 1041 | puts("Usage: tomcast [opts] -c config_file"); 1042 | puts(""); 1043 | puts(" Options:"); 1044 | puts("\t-c file\t\tChannels configuration file"); 1045 | puts("\t-i ident\tServer ident. Must be formated as PROVIDER/SERVER"); 1046 | puts("\t-d pidfile\tDaemonize and write daemon pid into pidfile"); 1047 | puts("\t-t ttl\t\tSet multicast ttl (default: 1)"); 1048 | puts("\t-o ip\t\tOutput interface address (default: 0.0.0.0)"); 1049 | puts("\t-l host\t\tSyslog host (default: disabled)"); 1050 | puts("\t-L port\t\tSyslog port (default: 514)"); 1051 | puts("\t-R\t\tSend reset packets when changing sources."); 1052 | puts("\t-E\t\tDetect encrypted input (default: false)"); 1053 | puts("\t-I\t\tHibernate channels at start (default: false)"); 1054 | puts(""); 1055 | puts(" Web server options:"); 1056 | puts("\t-b addr\t\tLocal IP address to bind. (default: 0.0.0.0)"); 1057 | puts("\t-p port\t\tPort to listen. (default: disabled)"); 1058 | puts(""); 1059 | } 1060 | 1061 | void set_ident(char *new_ident, struct config *cfg) { 1062 | cfg->ident = new_ident; 1063 | cfg->logident = strdup(new_ident); 1064 | char *c = cfg->logident; 1065 | while (*c) { 1066 | if (*c=='/') 1067 | *c='-'; 1068 | c++; 1069 | } 1070 | } 1071 | 1072 | void parse_options(int argc, char **argv, struct config *cfg) { 1073 | int j, ttl; 1074 | cfg->server_socket = -1; 1075 | cfg->logport = 514; 1076 | pthread_mutex_init(&cfg->channels_lock, NULL); 1077 | while ((j = getopt(argc, argv, "i:b:p:c:d:t:o:l:L:REIHh")) != -1) { 1078 | switch (j) { 1079 | case 'b': 1080 | cfg->server_addr = optarg; 1081 | break; 1082 | case 'p': 1083 | cfg->server_port = atoi(optarg); 1084 | break; 1085 | case 'i': 1086 | set_ident(optarg, cfg); 1087 | break; 1088 | case 'c': 1089 | cfg->channels_file = optarg; 1090 | break; 1091 | case 'd': 1092 | cfg->pidfile = optarg; 1093 | break; 1094 | case 'o': 1095 | if (inet_aton(optarg, &output_intf) == 0) { 1096 | fprintf(stderr, "Invalid interface address: %s\n", optarg); 1097 | exit(1); 1098 | } 1099 | break; 1100 | case 't': 1101 | ttl = atoi(optarg); 1102 | multicast_ttl = (ttl && ttl < 127) ? ttl : 1; 1103 | break; 1104 | case 'l': 1105 | cfg->loghost = optarg; 1106 | cfg->syslog_active = 1; 1107 | break; 1108 | case 'L': 1109 | cfg->logport = atoi(optarg); 1110 | break; 1111 | case 'R': 1112 | send_reset_opt = 1; 1113 | break; 1114 | case 'E': 1115 | cfg->detect_encrypted_input = 1; 1116 | break; 1117 | case 'I': 1118 | init_chan_stop_opt = 1; 1119 | break; 1120 | case 'H': 1121 | case 'h': 1122 | show_usage(0); 1123 | exit(0); 1124 | break; 1125 | } 1126 | } 1127 | 1128 | if (!cfg->channels_file) { 1129 | show_usage(0); 1130 | fprintf(stderr, "ERROR: No channels file is set (use -c option).\n"); 1131 | exit(1); 1132 | } 1133 | 1134 | if (!cfg->ident) { 1135 | set_ident("unixsol/tomcast", cfg); 1136 | } 1137 | 1138 | printf("Configuration:\n"); 1139 | printf("\tServer ident : %s\n", cfg->ident); 1140 | printf("\tChannels file : %s\n", cfg->channels_file); 1141 | printf("\tOutput iface addr : %s\n", inet_ntoa(output_intf)); 1142 | printf("\tMulticast ttl : %d\n", multicast_ttl); 1143 | if (cfg->syslog_active) { 1144 | printf("\tSyslog host : %s\n", cfg->loghost); 1145 | printf("\tSyslog port : %d\n", cfg->logport); 1146 | } else { 1147 | printf("\tSyslog disabled.\n"); 1148 | } 1149 | if (send_reset_opt) 1150 | printf("\tSend reset packets.\n"); 1151 | if (cfg->detect_encrypted_input) 1152 | printf("\tDetect encrypted input.\n"); 1153 | if (init_chan_stop_opt) 1154 | printf("\tStart channels in hibernation.\n"); 1155 | if (cfg->pidfile) { 1156 | printf("\tDaemonize : %s\n", cfg->pidfile); 1157 | } else { 1158 | printf("\tDo not daemonize.\n"); 1159 | } 1160 | if (cfg->server_port) { 1161 | init_server_socket(cfg->server_addr, cfg->server_port, &cfg->server, &cfg->server_socket); 1162 | printf("\tStarting web srv : http://%s:%d/status (sock: %d)\n", 1163 | cfg->server_addr ? cfg->server_addr : "*", cfg->server_port, cfg->server_socket); 1164 | } else { 1165 | printf("\tNo web server\n"); 1166 | } 1167 | } 1168 | 1169 | void init_vars(struct config *cfg) { 1170 | cfg->restreamer = list_new("restreamer"); 1171 | regcomp(&http_response, "^HTTP/1.[0-1] (([0-9]{3}) .*)", REG_EXTENDED); 1172 | memset(&TS_NULL_FRAME, 0xff, FRAME_PACKET_SIZE); 1173 | int i; 1174 | for (i=0; ichanconf, lc, lctmp) { 1187 | CHANNEL *c = lc->data; 1188 | int restreamer_active = 0; 1189 | list_lock(cfg->restreamer); 1190 | list_for_each(cfg->restreamer, lr, lrtmp) { 1191 | RESTREAMER *r = lr->data; 1192 | if (strcmp(r->name, c->name)==0) { 1193 | restreamer_active = 1; 1194 | break; 1195 | } 1196 | } 1197 | list_unlock(cfg->restreamer); 1198 | if (!restreamer_active) { 1199 | RESTREAMER *nr = new_restreamer(c->name, c); 1200 | if (nr->clientsock < 0) { 1201 | LOGf("Error creating proxy socket for %s\n", c->name); 1202 | free_restreamer(nr); 1203 | } else { 1204 | list_add(cfg->restreamer, nr); 1205 | if (pthread_create(&nr->thread, NULL, &proxy_ts_stream, nr) == 0) { 1206 | spawned++; 1207 | pthread_detach(nr->thread); 1208 | } else { 1209 | LOGf("Error creating proxy for %s\n", c->name); 1210 | } 1211 | } 1212 | } 1213 | } 1214 | LOGf("INFO : %d proxy threads spawned\n", spawned); 1215 | } 1216 | 1217 | void kill_proxy_threads(struct config *cfg) { 1218 | LNODE *l, *tmp; 1219 | int killed = 0; 1220 | list_lock(cfg->restreamer); 1221 | list_for_each(cfg->restreamer, l, tmp) { 1222 | RESTREAMER *r = l->data; 1223 | r->dienow = 1; 1224 | killed++; 1225 | } 1226 | list_unlock(cfg->restreamer); 1227 | LOGf("INFO : %d proxy threads killed\n", killed); 1228 | } 1229 | 1230 | int keep_going = 1; 1231 | 1232 | void signal_quit(int sig) { 1233 | keep_going = 0; 1234 | kill_proxy_threads(&config); 1235 | usleep(500000); 1236 | LOGf("KILL : Signal %i | %s %s (%s)\n", sig, server_sig, server_ver, config.ident); 1237 | usleep(100000); 1238 | log_close(); 1239 | if (config.pidfile && strlen(config.pidfile)) 1240 | unlink(config.pidfile); 1241 | signal(sig, SIG_DFL); 1242 | raise(sig); 1243 | } 1244 | 1245 | struct config *get_config(void) { 1246 | return &config; 1247 | } 1248 | 1249 | void do_reconnect(int sig) { 1250 | (void)sig; 1251 | LNODE *l, *tmp; 1252 | list_lock(config.restreamer); 1253 | list_for_each(config.restreamer, l, tmp) { 1254 | RESTREAMER *r = l->data; 1255 | r->reconnect = 1; 1256 | } 1257 | list_unlock(config.restreamer); 1258 | } 1259 | 1260 | void do_reconf(int sig) { 1261 | (void)sig; 1262 | load_channels_config(&config); 1263 | spawn_proxy_threads(&config); 1264 | } 1265 | 1266 | void init_signals(void) { 1267 | signal(SIGCHLD, SIG_IGN); 1268 | signal(SIGPIPE, SIG_IGN); 1269 | 1270 | signal(SIGHUP , do_reconf); 1271 | signal(SIGUSR1, do_reconnect); 1272 | 1273 | signal(SIGINT , signal_quit); 1274 | signal(SIGTERM, signal_quit); 1275 | } 1276 | 1277 | void do_daemonize(struct config *cfg) { 1278 | if (!cfg->pidfile) 1279 | return; 1280 | fprintf(stderr, "Daemonizing.\n"); 1281 | pid_t pid = fork(); 1282 | if (pid > 0) { 1283 | FILE *F = fopen(cfg->pidfile,"w"); 1284 | if (F) { 1285 | fprintf(F,"%i\n",pid); 1286 | fclose(F); 1287 | } 1288 | exit(0); 1289 | } 1290 | // Child process continues... 1291 | setsid(); // request a new session (job control) 1292 | freopen("/dev/null", "r", stdin); 1293 | freopen("/dev/null", "w", stdout); 1294 | freopen("/dev/null", "w", stderr); 1295 | } 1296 | 1297 | /* Must be called after daemonize! */ 1298 | void init_logger(struct config *cfg) { 1299 | if (cfg->syslog_active) 1300 | fprintf(stderr, "Logging to %s:%d\n", cfg->loghost, cfg->logport); 1301 | log_init(cfg->logident, cfg->syslog_active, cfg->pidfile == NULL, cfg->loghost, cfg->logport); 1302 | } 1303 | 1304 | int main(int argc, char **argv) { 1305 | set_http_response_server_ident(server_sig, server_ver); 1306 | show_usage(1); // Show copyright and version 1307 | init_vars(&config); 1308 | parse_options(argc, argv, &config); 1309 | do_daemonize(&config); 1310 | init_logger(&config); 1311 | init_signals(); 1312 | 1313 | LOGf("INIT : %s %s (%s)\n" , server_sig, server_ver, config.ident); 1314 | 1315 | load_channels_config(&config); 1316 | spawn_proxy_threads(&config); 1317 | web_server_start(&config); 1318 | 1319 | do { 1320 | sleep(60); 1321 | } while(1); 1322 | 1323 | signal_quit(15); 1324 | exit(0); 1325 | } 1326 | --------------------------------------------------------------------------------