// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2016, 2017 John Langner, WB2OSZ // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // /*------------------------------------------------------------------ * * Name: cdigipeater.c * * Purpose: Act as an digital repeater for connected AX.25 mode. * Similar digipeater.c is for APRS. * * * Description: Decide whether the specified packet should * be digipeated. Put my callsign in the digipeater field used. * * APRS and connected mode were two split into two * separate files. Yes, there is duplicate code but they * are significantly different and I thought it would be * too confusing to munge them together. * * References: The Ax.25 protocol barely mentions digipeaters and * and doesn't describe how they should work. * *------------------------------------------------------------------*/ #define CDIGIPEATER_C #include "direwolf.h" #include #include #include #include #include /* for isdigit, isupper */ #include "regex.h" #include #include "ax25_pad.h" #include "cdigipeater.h" #include "textcolor.h" #include "tq.h" #include "pfilter.h" static packet_t cdigipeat_match (int from_chan, packet_t pp, char *mycall_rec, char *mycall_xmit, int has_alias, regex_t *alias, int to_chan, char *cfilter_str); /* * Keep pointer to configuration options. * Set by cdigipeater_init and used later. */ static struct audio_s *save_audio_config_p; static struct cdigi_config_s *save_cdigi_config_p; /* * Maintain count of packets digipeated for each combination of from/to channel. */ static int cdigi_count[MAX_CHANS][MAX_CHANS]; int cdigipeater_get_count (int from_chan, int to_chan) { return (cdigi_count[from_chan][to_chan]); } /*------------------------------------------------------------------------------ * * Name: cdigipeater_init * * Purpose: Initialize with stuff from configuration file. * * Inputs: p_audio_config - Configuration for audio channels. * * p_cdigi_config - Connected Digipeater configuration details. * * Outputs: Save pointers to configuration for later use. * * Description: Called once at application startup time. * *------------------------------------------------------------------------------*/ void cdigipeater_init (struct audio_s *p_audio_config, struct cdigi_config_s *p_cdigi_config) { save_audio_config_p = p_audio_config; save_cdigi_config_p = p_cdigi_config; } /*------------------------------------------------------------------------------ * * Name: cdigipeater * * Purpose: Re-transmit packet if it matches the rules. * * Inputs: chan - Radio channel where it was received. * * pp - Packet object. * * Returns: None. * *------------------------------------------------------------------------------*/ void cdigipeater (int from_chan, packet_t pp) { int to_chan; // Connected mode is allowed only for channels with internal modem. // It probably wouldn't matter for digipeating but let's keep that rule simple and consistent. if ( from_chan < 0 || from_chan >= MAX_CHANS || save_audio_config_p->chan_medium[from_chan] != MEDIUM_RADIO) { text_color_set(DW_COLOR_ERROR); dw_printf ("cdigipeater: Did not expect to receive on invalid channel %d.\n", from_chan); return; } /* * First pass: Look at packets being digipeated to same channel. * * There was a reason for two passes for APRS. * Might not have a benefit here. */ for (to_chan=0; to_chanenabled[from_chan][to_chan]) { if (to_chan == from_chan) { packet_t result; result = cdigipeat_match (from_chan, pp, save_audio_config_p->achan[from_chan].mycall, save_audio_config_p->achan[to_chan].mycall, save_cdigi_config_p->has_alias[from_chan][to_chan], &(save_cdigi_config_p->alias[from_chan][to_chan]), to_chan, save_cdigi_config_p->cfilter_str[from_chan][to_chan]); if (result != NULL) { tq_append (to_chan, TQ_PRIO_0_HI, result); cdigi_count[from_chan][to_chan]++; } } } } /* * Second pass: Look at packets being digipeated to different channel. */ for (to_chan=0; to_chanenabled[from_chan][to_chan]) { if (to_chan != from_chan) { packet_t result; result = cdigipeat_match (from_chan, pp, save_audio_config_p->achan[from_chan].mycall, save_audio_config_p->achan[to_chan].mycall, save_cdigi_config_p->has_alias[from_chan][to_chan], &(save_cdigi_config_p->alias[from_chan][to_chan]), to_chan, save_cdigi_config_p->cfilter_str[from_chan][to_chan]); if (result != NULL) { tq_append (to_chan, TQ_PRIO_0_HI, result); cdigi_count[from_chan][to_chan]++; } } } } } /* end cdigipeater */ /*------------------------------------------------------------------------------ * * Name: cdigipeat_match * * Purpose: A simple digipeater for connected mode AX.25. * * Input: pp - Pointer to a packet object. * * mycall_rec - Call of my station, with optional SSID, * associated with the radio channel where the * packet was received. * * mycall_xmit - Call of my station, with optional SSID, * associated with the radio channel where the * packet is to be transmitted. Could be the same as * mycall_rec or different. * * has_alias - True if we have an alias. * * alias - Optional compiled pattern for my station aliases. * Do NOT attempt to use this if 'has_alias' is false. * * to_chan - Channel number that we are transmitting to. * * cfilter_str - Filter expression string for the from/to channel pair or NULL. * Note that only a subset of the APRS filters are applicable here. * * Returns: Packet object for transmission or NULL. * The original packet is not modified. The caller is responsible for freeing it. * We make a copy and return that modified copy! * This is very important because we could digipeat from one channel to many. * * Description: The packet will be digipeated if the next unused digipeater * field matches one of the following: * * - mycall_rec * - alias list * * APRS digipeating drops duplicates within 30 seconds but we don't do that here. * *------------------------------------------------------------------------------*/ static packet_t cdigipeat_match (int from_chan, packet_t pp, char *mycall_rec, char *mycall_xmit, int has_alias, regex_t *alias, int to_chan, char *cfilter_str) { int r; char repeater[AX25_MAX_ADDR_LEN]; int err; char err_msg[100]; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("cdigipeat_match (from_chan=%d, pp=%p, mycall_rec=%s, mycall_xmit=%s, has_alias=%d, alias=%p, to_chan=%d, cfilter_str=%s\n", from_chan, pp, mycall_rec, mycall_xmit, has_alias, alias, to_chan, cfilter_str); #endif /* * First check if filtering has been configured. * Note that we have three different config file filter commands: * * FILTER - APRS digipeating and IGate client side. * Originally this was the only one. * Should we change it to AFILTER to make it clearer? * CFILTER - Similar for connected moded digipeater. * IGFILTER - APRS-IS (IGate) server side - completely different. * Confusing with similar name but much different idea. * Maybe this should be renamed to SUBSCRIBE or something like that. * * Logically this should come later, after an address/alias match. * But here we only have to do it once. */ if (cfilter_str != NULL) { if (pfilter(from_chan, to_chan, cfilter_str, pp, 0) != 1) { return(NULL); } } /* * Find the first repeater station which doesn't have "has been repeated" set. * * r = index of the address position in the frame. */ r = ax25_get_first_not_repeated(pp); if (r < AX25_REPEATER_1) { return (NULL); // Nothing to do. } ax25_get_addr_with_ssid(pp, r, repeater); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("First unused digipeater is %s\n", repeater); #endif /* * First check for explicit use of my call. * Note that receive and transmit channels could have different callsigns. */ if (strcmp(repeater, mycall_rec) == 0) { packet_t result; result = ax25_dup (pp); assert (result != NULL); /* If using multiple radio channels, they could have different calls. */ ax25_set_addr (result, r, mycall_xmit); ax25_set_h (result, r); return (result); } /* * If we have an alias match, substitute MYCALL. */ if (has_alias) { #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("Checking %s for alias match.\n", repeater); #endif err = regexec(alias,repeater,0,NULL,0); if (err == 0) { packet_t result; result = ax25_dup (pp); assert (result != NULL); ax25_set_addr (result, r, mycall_xmit); ax25_set_h (result, r); return (result); } else if (err != REG_NOMATCH) { regerror(err, alias, err_msg, sizeof(err_msg)); text_color_set (DW_COLOR_ERROR); dw_printf ("%s\n", err_msg); } } else { #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("No alias was specified.\n"); #endif } /* * Don't repeat it if we get here. */ return (NULL); } /* end cdigipeat_match */ /* end cdigipeater.c */