diff --git a/Makefile.linux b/Makefile.linux index 2b4e42e..8bd5244 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -584,21 +584,21 @@ check : dtest ttest tttexttest pftest tlmtest lltest enctest kisstest check-mode # Can we encode and decode at popular data rates? check-modem1200 : gen_packets atest - gen_packets -n 100 -o /tmp/test1.wav - atest -F0 -PE -L70 -G71 /tmp/test1.wav - atest -F1 -PE -L73 -G75 /tmp/test1.wav + ./gen_packets -n 100 -o /tmp/test1.wav + ./atest -F0 -PE -L70 -G71 /tmp/test1.wav + ./atest -F1 -PE -L73 -G75 /tmp/test1.wav #rm /tmp/test1.wav check-modem300 : gen_packets atest - gen_packets -B300 -n 100 -o /tmp/test3.wav - atest -B300 -F0 -L68 -G69 /tmp/test3.wav - atest -B300 -F1 -L73 -G75 /tmp/test3.wav + ./gen_packets -B300 -n 100 -o /tmp/test3.wav + ./atest -B300 -F0 -L68 -G69 /tmp/test3.wav + ./atest -B300 -F1 -L73 -G75 /tmp/test3.wav rm /tmp/test3.wav check-modem9600 : gen_packets atest - gen_packets -B9600 -n 100 -o /tmp/test9.wav - atest -B9600 -F0 -L57 -G59 /tmp/test9.wav - atest -B9600 -F1 -L66 -G67 /tmp/test9.wav + ./gen_packets -B9600 -n 100 -o /tmp/test9.wav + ./atest -B9600 -F0 -L57 -G59 /tmp/test9.wav + ./atest -B9600 -F1 -L66 -G67 /tmp/test9.wav rm /tmp/test9.wav @@ -606,7 +606,8 @@ check-modem9600 : gen_packets atest # Unit test for inner digipeater algorithm .PHONY : dtest -dtest : digipeater.c pfilter.o ax25_pad.o dedupe.o fcs_calc.o tq.o textcolor.o \ +dtest : digipeater.c dedupe.c \ + pfilter.o ax25_pad.o fcs_calc.o tq.o textcolor.o \ decode_aprs.o dwgpsnmea.o dwgps.o dwgpsd.o serial_port.o latlong.o telemetry.o symbols.o tt_text.o misc.a $(CC) $(CFLAGS) -DDIGITEST -o $@ $^ $(LDFLAGS) ./dtest diff --git a/Makefile.win b/Makefile.win index 4fcda6f..a12ede8 100644 --- a/Makefile.win +++ b/Makefile.win @@ -283,7 +283,8 @@ atest9 : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.c # Unit test for inner digipeater algorithm .PHONY: dtest -dtest : digipeater.c pfilter.o ax25_pad.o dedupe.o fcs_calc.o tq.o textcolor.o \ +dtest : digipeater.c dedupe.c \ + pfilter.o ax25_pad.o fcs_calc.o tq.o textcolor.o \ decode_aprs.o dwgpsnmea.o dwgps.o serial_port.o latlong.o telemetry.o symbols.o tt_text.o misc.a regex.a $(CC) $(CFLAGS) -DDIGITEST -o $@ $^ ./dtest diff --git a/ax25_pad.c b/ax25_pad.c index 0d894ff..abaa617 100644 --- a/ax25_pad.c +++ b/ax25_pad.c @@ -476,7 +476,7 @@ packet_t ax25_from_text (char *monitor, int strict) return (NULL); } - if ( ! ax25_parse_addr (pa, strict, atemp, &ssid_temp, &heard_temp)) { + if ( ! ax25_parse_addr (AX25_SOURCE, pa, strict, atemp, &ssid_temp, &heard_temp)) { text_color_set(DW_COLOR_ERROR); dw_printf ("Failed to create packet from text. Bad source address\n"); ax25_delete (this_p); @@ -500,7 +500,7 @@ packet_t ax25_from_text (char *monitor, int strict) return (NULL); } - if ( ! ax25_parse_addr (pa, strict, atemp, &ssid_temp, &heard_temp)) { + if ( ! ax25_parse_addr (AX25_DESTINATION, pa, strict, atemp, &ssid_temp, &heard_temp)) { text_color_set(DW_COLOR_ERROR); dw_printf ("Failed to create packet from text. Bad destination address\n"); ax25_delete (this_p); @@ -524,7 +524,7 @@ packet_t ax25_from_text (char *monitor, int strict) // JWL 10:38 this_p->num_addr++; - if ( ! ax25_parse_addr (pa, strict, atemp, &ssid_temp, &heard_temp)) { + if ( ! ax25_parse_addr (k, pa, strict, atemp, &ssid_temp, &heard_temp)) { text_color_set(DW_COLOR_ERROR); dw_printf ("Failed to create packet from text. Bad digipeater address\n"); ax25_delete (this_p); @@ -582,12 +582,9 @@ packet_t ax25_from_frame_debug (unsigned char *fbuf, int flen, alevel_t alevel, packet_t ax25_from_frame (unsigned char *fbuf, int flen, alevel_t alevel) #endif { - //unsigned char *pf; packet_t this_p; - //int a; - //int addr_bytes; - + /* * First make sure we have an acceptable length: * @@ -687,7 +684,10 @@ packet_t ax25_dup (packet_t copy_from) * * Purpose: Parse address with optional ssid. * - * Inputs: in_addr - Input such as "WB2OSZ-15*" + * Inputs: position - AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER_1... + * Used for more specific error message. -1 if not used. + * + * in_addr - Input such as "WB2OSZ-15*" * * strict - TRUE for strict checking (6 characters, no lower case, * SSID must be in range of 0 to 15). @@ -710,8 +710,12 @@ packet_t ax25_dup (packet_t copy_from) * *------------------------------------------------------------------------------*/ +static const char *position_name[1 + AX25_MAX_ADDRS] = { + "", "Destination ", "Source ", + "Digi1 ", "Digi2 ", "Digi3 ", "Digi4 ", + "Digi5 ", "Digi6 ", "Digi7 ", "Digi8 " }; -int ax25_parse_addr (char *in_addr, int strict, char *out_addr, int *out_ssid, int *out_heard) +int ax25_parse_addr (int position, char *in_addr, int strict, char *out_addr, int *out_ssid, int *out_heard) { char *p; char sstr[8]; /* Should be 1 or 2 digits for SSID. */ @@ -722,22 +726,33 @@ int ax25_parse_addr (char *in_addr, int strict, char *out_addr, int *out_ssid, i *out_ssid = 0; *out_heard = 0; + if (strict && strlen(in_addr) >= 2 && strncmp(in_addr, "qA", 2) == 0) { + + text_color_set(DW_COLOR_ERROR); + dw_printf ("%sAddress \"%s\" is a \"q-construct\" used for communicating\n", position_name[position], in_addr); + dw_printf ("with APRS Internet Servers. It was not expected here.\n"); + } + //dw_printf ("ax25_parse_addr in: %s\n", in_addr); + if (position < -1) position = -1; + if (position > AX25_REPEATER_8) position = AX25_REPEATER_8; + position++; /* Adjust for position_name above. */ + maxlen = strict ? 6 : (AX25_MAX_ADDR_LEN-1); p = in_addr; i = 0; for (p = in_addr; isalnum(*p); p++) { if (i >= maxlen) { text_color_set(DW_COLOR_ERROR); - dw_printf ("Address is too long. \"%s\" has more than %d characters.\n", in_addr, maxlen); + dw_printf ("%sAddress is too long. \"%s\" has more than %d characters.\n", position_name[position], in_addr, maxlen); return 0; } out_addr[i++] = *p; out_addr[i] = '\0'; if (strict && islower(*p)) { text_color_set(DW_COLOR_ERROR); - dw_printf ("Address has lower case letters. \"%s\" must be all upper case.\n", in_addr); + dw_printf ("%sAddress has lower case letters. \"%s\" must be all upper case.\n", position_name[position], in_addr); return 0; } } @@ -748,21 +763,21 @@ int ax25_parse_addr (char *in_addr, int strict, char *out_addr, int *out_ssid, i for (p++; isalnum(*p); p++) { if (j >= 2) { text_color_set(DW_COLOR_ERROR); - dw_printf ("SSID is too long. SSID part of \"%s\" has more than 2 characters.\n", in_addr); + dw_printf ("%sSSID is too long. SSID part of \"%s\" has more than 2 characters.\n", position_name[position], in_addr); return 0; } sstr[j++] = *p; sstr[j] = '\0'; if (strict && ! isdigit(*p)) { text_color_set(DW_COLOR_ERROR); - dw_printf ("SSID must be digits. \"%s\" has letters in SSID.\n", in_addr); + dw_printf ("%sSSID must be digits. \"%s\" has letters in SSID.\n", position_name[position], in_addr); return 0; } } k = atoi(sstr); if (k < 0 || k > 15) { text_color_set(DW_COLOR_ERROR); - dw_printf ("SSID out of range. SSID of \"%s\" not in range of 0 to 15.\n", in_addr); + dw_printf ("%sSSID out of range. SSID of \"%s\" not in range of 0 to 15.\n", position_name[position], in_addr); return 0; } *out_ssid = k; @@ -775,7 +790,7 @@ int ax25_parse_addr (char *in_addr, int strict, char *out_addr, int *out_ssid, i if (*p != '\0') { text_color_set(DW_COLOR_ERROR); - dw_printf ("Invalid character \"%c\" found in address \"%s\".\n", *p, in_addr); + dw_printf ("Invalid character \"%c\" found in %saddress \"%s\".\n", *p, position_name[position], in_addr); return 0; } @@ -786,6 +801,73 @@ int ax25_parse_addr (char *in_addr, int strict, char *out_addr, int *out_ssid, i } /* end ax25_parse_addr */ +/*------------------------------------------------------------------- + * + * Name: ax25_check_addresses + * + * Purpose: Check addresses of given packet and print message if any issues. + * We call this when receiving and transmitting. + * + * Inputs: pp - packet object pointer. + * + * Errors: Print error message. + * + * Returns: 1 for all valid. 0 if not. + * + * Examples: I was surprised to get this from an APRS-IS server with + * a lower case source address. + * + * n1otx>APRS,TCPIP*,qAC,THIRD:@141335z4227.48N/07111.73W_348/005g014t044r000p000h60b10075.wview_5_20_2 + * + * I haven't gotten to the bottom of this yet but it sounds + * like "q constructs" are somehow getting on to the air when + * they should only appear in conversations with IGate servers. + * + * https://groups.yahoo.com/neo/groups/direwolf_packet/conversations/topics/678 + * + * WB0VGI-7>APDW12,W0YC-5*,qAR,AE0RF-10:}N0DZQ-10>APWW10,TCPIP,WB0VGI-7*:;145.230MN*080306z4607.62N/09230.58WrKE0ACL/R 145.230- T146.2 (Pine County ARES) + * + * Typical result: + * + * Digipeater WIDE2 (probably N3LEE-4) audio level = 28(10/6) [NONE] __||||||| + * [0.5] VE2DJE-9>P_0_P?,VE2PCQ-3,K1DF-7,N3LEE-4,WIDE2*:'{S+l <0x1c>>/ + * Invalid character "_" in MIC-E destination/latitude. + * Invalid character "_" in MIC-E destination/latitude. + * Invalid character "?" in MIC-E destination/latitude. + * Invalid MIC-E N/S encoding in 4th character of destination. + * Invalid MIC-E E/W encoding in 6th character of destination. + * MIC-E, normal car (side view), Unknown manufacturer, Returning + * N 00 00.0000, E 005 55.1500, 0 MPH + * Invalid character "_" found in Destination address "P_0_P?". + * + * *** The origin and journey of this packet should receive some scrutiny. *** + * + *--------------------------------------------------------------------*/ + +int ax25_check_addresses (packet_t pp) +{ + int n; + char addr[AX25_MAX_ADDR_LEN]; + char ignore1[AX25_MAX_ADDR_LEN]; + int ignore2, ignore3; + int all_ok = 1; + + for (n = 0; n < ax25_get_num_addr(pp); n++) { + ax25_get_addr_with_ssid (pp, n, addr); + all_ok &= ax25_parse_addr (n, addr, 1, ignore1, &ignore2, &ignore3); + } + + if (! all_ok) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("\n"); + dw_printf ("*** The origin and journey of this packet should receive some scrutiny. ***\n"); + dw_printf ("\n"); + } + + return (all_ok); +} /* end ax25_check_addresses */ + + /*------------------------------------------------------------------------------ * * Name: ax25_unwrap_third_party @@ -862,7 +944,7 @@ void ax25_set_addr (packet_t this_p, int n, char *ad) /* * Set existing address position. */ - ax25_parse_addr (ad, 0, atemp, &ssid_temp, &heard_temp); + ax25_parse_addr (n, ad, 0, atemp, &ssid_temp, &heard_temp); memset (this_p->frame_data + n*7, ' ' << 1, 6); @@ -951,7 +1033,11 @@ void ax25_insert_addr (packet_t this_p, int n, char *ad) SET_LAST_ADDR_FLAG; - ax25_parse_addr (ad, 0, atemp, &ssid_temp, &heard_temp); + // Why aren't we setting 'strict' here? + // Messages from IGate have q-constructs. + // We use this to parse it and later remove unwanted parts. + + ax25_parse_addr (n, ad, 0, atemp, &ssid_temp, &heard_temp); memset (this_p->frame_data + n*7, ' ' << 1, 6); for (i=0; i<6 && atemp[i] != '\0'; i++) { this_p->frame_data[n*7+i] = atemp[i] << 1; @@ -1160,8 +1246,9 @@ void ax25_get_addr_with_ssid (packet_t this_p, int n, char *station) if (ssid != 0) { snprintf (sstr, sizeof(sstr), "-%d", ssid); strlcat (station, sstr, 10); - } -} + } + +} /* end ax25_get_addr_with_ssid */ /*------------------------------------------------------------------------------ @@ -2064,6 +2151,27 @@ int ax25_get_pid (packet_t this_p) * There is a very very small probability that two unrelated * packets will result in the same checksum, and the * undesired dropping of the packet. + * + * There is a 1 / 65536 chance of getting a false positive match + * which is good enough for this application. + * We could reduce that with a 32 bit CRC instead of reusing + * code from the AX.25 frame CRC calculation. + * + * Version 1.3: We exclude any trailing CR/LF at the end of the info part + * so we can detect duplicates that are received only over the + * air and those which have gone thru an IGate where the process + * removes any trailing CR/LF. Example: + * + * Original via RF only: + * W1TG-1>APU25N,N3LEE-10*,WIDE2-1: + * + * When we get the same thing via APRS-IS: + * W1TG-1>APU25N,K1FFK,WIDE2*,qAR,WB2ZII-15:= 1 && (pinfo[info_len-1] == '\r' || + pinfo[info_len-1] == '\n' || + pinfo[info_len-1] == ' ')) { + + // Temporary for debugging! + + // if (pinfo[info_len-1] == ' ') { + // text_color_set(DW_COLOR_ERROR); + // dw_printf ("DEBUG: ax25_dedupe_crc ignoring trailing space.\n"); + // } + + info_len--; + } + crc = 0xffff; crc = crc16((unsigned char *)src, strlen(src), crc); crc = crc16((unsigned char *)dest, strlen(dest), crc); @@ -2159,6 +2281,11 @@ unsigned short ax25_m_m_crc (packet_t pp) * as hexadecimal for troubleshooting? Maybe an option so the * packet raw data is in hexadecimal but an extracted * comment displays UTF-8? Or a command line option for only ASCII? + * + * Trailing space: + * I recently noticed a case where a packet has space character + * at the end. If the last character of the line is a space, + * this will be displayed in hexadecimal to make it obvious. * *------------------------------------------------------------------*/ @@ -2184,7 +2311,12 @@ void ax25_safe_print (char *pstr, int len, int ascii_only) { ch = *((unsigned char *)pstr); - if (ch < ' ' || ch == 0x7f || ch == 0xfe || ch == 0xff || + if (ch == ' ' && (len == 1 || pstr[1] == '\0')) { + + snprintf (safe_str + safe_len, sizeof(safe_str)-safe_len, "<0x%02x>", ch); + safe_len += 6; + } + else if (ch < ' ' || ch == 0x7f || ch == 0xfe || ch == 0xff || (ascii_only && ch >= 0x80) ) { /* Control codes and delete. */ @@ -2192,7 +2324,7 @@ void ax25_safe_print (char *pstr, int len, int ascii_only) /* "Byte Order Mark" (BOM) at the beginning. */ snprintf (safe_str + safe_len, sizeof(safe_str)-safe_len, "<0x%02x>", ch); - safe_len += 6; + safe_len += 6; } else { /* Let everything else thru so we can handle UTF-8 */ diff --git a/ax25_pad.h b/ax25_pad.h index d133808..994a082 100644 --- a/ax25_pad.h +++ b/ax25_pad.h @@ -307,7 +307,8 @@ extern void ax25_delete (packet_t pp); #endif -extern int ax25_parse_addr (char *in_addr, int strict, char *out_addr, int *out_ssid, int *out_heard); +extern int ax25_parse_addr (int position, char *in_addr, int strict, char *out_addr, int *out_ssid, int *out_heard); +extern int ax25_check_addresses (packet_t pp); extern packet_t ax25_unwrap_third_party (packet_t from_pp); diff --git a/config.c b/config.c index 72e60c9..2cd172f 100644 --- a/config.c +++ b/config.c @@ -742,8 +742,8 @@ void config_init (char *fname, struct audio_s *p_audio_config, memset (p_igate_config, 0, sizeof(struct igate_config_s)); p_igate_config->t2_server_port = DEFAULT_IGATE_PORT; p_igate_config->tx_chan = -1; /* IS->RF not enabled */ - p_igate_config->tx_limit_1 = 6; - p_igate_config->tx_limit_5 = 20; + p_igate_config->tx_limit_1 = IGATE_TX_LIMIT_1_DEFAULT; + p_igate_config->tx_limit_5 = IGATE_TX_LIMIT_5_DEFAULT; /* People find this confusing. */ @@ -3165,7 +3165,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, if (islower(*p)) *p = toupper(*p); } - if ( ! ax25_parse_addr(t, 1, method, &ssid, &heard)) { + if ( ! ax25_parse_addr(-1, t, 1, method, &ssid, &heard)) { continue; // function above prints any error message } @@ -3409,18 +3409,22 @@ void config_init (char *fname, struct audio_s *p_audio_config, continue; } - /* limits of 20 and 100 are unfriendly but not insane. */ - n = atoi(t); - if (n >= 1 && n <= 20) { + if (n < 1) { + p_igate_config->tx_limit_1 = 1; + } + else if (n <= IGATE_TX_LIMIT_1_MAX) { p_igate_config->tx_limit_1 = n; } else { + p_igate_config->tx_limit_1 = IGATE_TX_LIMIT_1_MAX; text_color_set(DW_COLOR_ERROR); - dw_printf ("Line %d: Invalid one minute transmit limit. Using %d.\n", + dw_printf ("Line %d: One minute transmit limit has been reduced to %d.\n", line, p_igate_config->tx_limit_1); + dw_printf ("You won't make friends by setting a limit this high.\n"); } + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); @@ -3429,13 +3433,18 @@ void config_init (char *fname, struct audio_s *p_audio_config, } n = atoi(t); - if (n >= 1 && n <= 100) { + if (n < 1) { + p_igate_config->tx_limit_5 = 1; + } + else if (n <= IGATE_TX_LIMIT_5_MAX) { p_igate_config->tx_limit_5 = n; } else { + p_igate_config->tx_limit_5 = IGATE_TX_LIMIT_5_MAX; text_color_set(DW_COLOR_ERROR); - dw_printf ("Line %d: Invalid one minute transmit limit. Using %d.\n", + dw_printf ("Line %d: Five minute transmit limit has been reduced to %d.\n", line, p_igate_config->tx_limit_5); + dw_printf ("You won't make friends by setting a limit this high.\n"); } } diff --git a/decode_aprs.c b/decode_aprs.c index 807dcd9..b259aa4 100644 --- a/decode_aprs.c +++ b/decode_aprs.c @@ -193,6 +193,7 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet) A->g_footprint_radius = G_UNKNOWN; + /* * Extract source and destination including the SSID. */ @@ -4552,6 +4553,12 @@ int main (int argc, char *argv[]) decode_aprs_print (&A); + /* + * Perform validity check on each address. + * This should print an error message if any issues. + */ + (void)ax25_check_addresses(pp); + // Send to log file? // if (logdir != NULL && *logdir != '\0') { diff --git a/dedupe.c b/dedupe.c index d7c69d7..66a035f 100644 --- a/dedupe.c +++ b/dedupe.c @@ -108,6 +108,9 @@ #include "dedupe.h" #include "fcs_calc.h" #include "textcolor.h" +#ifndef DIGITEST +#include "igate.h" +#endif /*------------------------------------------------------------------------------ @@ -205,6 +208,14 @@ void dedupe_remember (packet_t pp, int chan) if (insert_next >= HISTORY_MAX) { insert_next = 0; } + + /* If we send something by digipeater, we don't */ + /* want to do it again if it comes from APRS-IS. */ + /* Not sure about the other way around. */ + +#ifndef DIGITEST + ig_to_tx_remember (pp, chan, 1); +#endif } diff --git a/direwolf.c b/direwolf.c index 51e531e..5e60a35 100644 --- a/direwolf.c +++ b/direwolf.c @@ -177,13 +177,14 @@ int main (int argc, char *argv[]) char input_file[80]; int t_opt = 1; /* Text color option. */ + int a_opt = 0; /* "-a n" interval, in seconds, for audio statistics report. 0 for none. */ + int d_k_opt = 0; /* "-d k" option for serial port KISS. Can be repeated for more detail. */ int d_n_opt = 0; /* "-d n" option for Network KISS. Can be repeated for more detail. */ int d_t_opt = 0; /* "-d t" option for Tracker. Can be repeated for more detail. */ int d_g_opt = 0; /* "-d g" option for GPS. Can be repeated for more detail. */ int d_o_opt = 0; /* "-d o" option for output control such as PTT and DCD. */ - int a_opt = 0; /* "-a n" interval, in seconds, for audio statistics report. 0 for none. */ - + int d_i_opt = 0; /* "-d i" option for IGate. Repeat for more detail */ strlcpy(l_opt, "", sizeof(l_opt)); @@ -233,6 +234,14 @@ int main (int argc, char *argv[]) dw_printf ("Dire Wolf DEVELOPMENT version %d.%d %s (%s)\n", MAJOR_VERSION, MINOR_VERSION, "G", __DATE__); //dw_printf ("Dire Wolf version %d.%d\n", MAJOR_VERSION, MINOR_VERSION); +#if defined(ENABLE_GPSD) // later or hamlib ... + dw_printf ("Includes optional support for: "); +#if defined(ENABLE_GPSD) + dw_printf (" gpsd"); +#endif + dw_printf ("\n"); +#endif + #if __WIN32__ SetConsoleCtrlHandler ((PHANDLER_ROUTINE)cleanup_win, TRUE); @@ -438,6 +447,7 @@ int main (int argc, char *argv[]) case 'w': nmea_set_debug (1); break; // not documented yet. case 'p': d_p_opt = 1; break; // TODO: packet dump for xmit side. case 'o': d_o_opt++; ptt_set_debug(d_o_opt); break; + case 'i': d_i_opt++; break; #if AX25MEMDEBUG case 'm': ax25memdebug_set(); break; // Track down memory leak. Not documented. #endif @@ -653,7 +663,7 @@ int main (int argc, char *argv[]) * Initialize the digipeater and IGate functions. */ digipeater_init (&audio_config, &digi_config); - igate_init (&audio_config, &igate_config, &digi_config); + igate_init (&audio_config, &igate_config, &digi_config, d_i_opt); /* * Provide the AGW & KISS socket interfaces for use by a client application. @@ -681,18 +691,19 @@ int main (int argc, char *argv[]) /* * Enable beaconing. + * Open log file first because "-dttt" (along with -l...) will + * log the tracker beacon transmissions with fake channel 999. */ + log_init(misc_config.logdir); beacon_init (&audio_config, &misc_config); - log_init(misc_config.logdir); /* * Get sound samples and decode them. * Use hot attribute for all functions called for every audio sample. */ - recv_init (&audio_config); recv_process (); @@ -898,6 +909,12 @@ void app_process_rec_packet (int chan, int subchan, packet_t pp, alevel_t alevel decode_aprs_print (&A); + /* + * Perform validity check on each address. + * This should print an error message if any issues. + */ + (void)ax25_check_addresses(pp); + // Send to log file. log_write (chan, &A, pp, alevel, retries); @@ -1038,6 +1055,7 @@ static void usage (char **argv) dw_printf (" g g = GPS interface.\n"); dw_printf (" t t = Tracker beacon.\n"); dw_printf (" o o = output controls such as PTT and DCD.\n"); + dw_printf (" i i = IGate.\n"); dw_printf (" -q Quiet (suppress output) options:\n"); dw_printf (" h h = Heard line with the audio level.\n"); dw_printf (" d d = Decoding of APRS packets.\n"); diff --git a/direwolf.conf b/direwolf.conf.save similarity index 98% rename from direwolf.conf rename to direwolf.conf.save index 1e1af46..9952702 100644 --- a/direwolf.conf +++ b/direwolf.conf.save @@ -318,7 +318,7 @@ TBEACON delay=0:30 every=0:20 SYMBOL=car FREQ=146.955 OFFSET=-0.600 TONE=74.4 # the "#" from the beginning of the line below. # -#DIGIPEAT 0 0 ^WIDE[3-7]-[1-7]$|^TEST$ ^WIDE[12]-[12]$ TRACE +DIGIPEAT 0 0 ^WIDE[3-7]-[1-7]$|^TEST$ ^WIDE[12]-[12]$ TRACE # See User Guide for more explanation of what this means and how # it can be customized for your particular needs. @@ -346,12 +346,12 @@ TBEACON delay=0:30 every=0:20 SYMBOL=car FREQ=146.955 OFFSET=-0.600 TONE=74.4 # asia.aprs2.net - for Asia # aunz.aprs2.net - for Oceania -#IGSERVER noam.aprs2.net +IGSERVER noam.aprs2.net # You also need to specify your login name and passcode. # Contact the author if you can't figure out how to generate the passcode. -#IGLOGIN WB2OSZ-5 123456 +IGLOGIN WB2OSZ-14 17845 # That's all you need for a receive only IGate which relays # messages from the local radio channel to the global servers. @@ -361,26 +361,28 @@ TBEACON delay=0:30 every=0:20 SYMBOL=car FREQ=146.955 OFFSET=-0.600 TONE=74.4 # forward it to an IGate server. This is done by using sendto=IG rather # than a radio channel number. Overlay R for receive only, T for two way. -#PBEACON sendto=IG delay=0:30 every=60:00 symbol="igate" overlay=R lat=42^37.14N long=071^20.83W +PBEACON sendto=IG delay=1:00 every=60:00 symbol="igate" overlay=R lat=42^37.14N long=071^20.83W #PBEACON sendto=IG delay=0:30 every=60:00 symbol="igate" overlay=T lat=42^37.14N long=071^20.83W # To relay messages from the Internet to radio, you need to add # one more option with the transmit channel number and a VIA path. -#IGTXVIA 0 WIDE1-1 +IGTXVIA 0 WIDE1-1 # You might want to apply a filter for what packets will be obtained from the server. # Read about filters here: http://www.aprs-is.net/javaprsfilter.aspx # Example, positions and objects within 50 km of my location: -#IGFILTER m/50 +IGFILTER m/100 # That is known as a server-side filter. It is processed by the IGate server. # You can also apply local filtering to limit what will be transmitted on the # RF side. For example, transmit only "messages" on channel 0 and weather # reports on channel 1. +filter 0 ig t/p + #FILTER IG 0 t/m #FILTER IG 1 t/wn @@ -389,7 +391,7 @@ TBEACON delay=0:30 every=0:20 SYMBOL=car FREQ=146.955 OFFSET=-0.600 TONE=74.4 # during 1 minute and 5 minute intervals. If a limit would # be exceeded, the packet is dropped and message is displayed in red. -IGTXLIMIT 6 10 +IGTXLIMIT 20 100 ############################################################# diff --git a/doc/Raspberry-Pi-APRS-Tracker.pdf b/doc/Raspberry-Pi-APRS-Tracker.pdf index edb2c22..60018d5 100644 Binary files a/doc/Raspberry-Pi-APRS-Tracker.pdf and b/doc/Raspberry-Pi-APRS-Tracker.pdf differ diff --git a/dwgpsd.c b/dwgpsd.c index a3f4835..d970a88 100644 --- a/dwgpsd.c +++ b/dwgpsd.c @@ -171,7 +171,7 @@ int dwgpsd_init (struct misc_config_s *pconfig, int debug) dwgps_info_t info; text_color_set(DW_COLOR_ERROR); - dw_printf ("Unable to connect to GPSD stream.\n"); + dw_printf ("Unable to connect to GPSD stream at %s:%s.\n", pconfig->gpsd_host, sport); dw_printf ("%s\n", gps_errstr(errno)); return (-1); diff --git a/igate.c b/igate.c index b126a9b..4373857 100644 --- a/igate.c +++ b/igate.c @@ -107,16 +107,15 @@ static void * connnect_thread (void *arg); static void * igate_recv_thread (void *arg); #endif -static void send_msg_to_server (char *msg); -static void xmit_packet (char *message); +static void send_msg_to_server (const char *msg); +static void xmit_packet (char *message, int chan); static void rx_to_ig_init (void); static void rx_to_ig_remember (packet_t pp); static int rx_to_ig_allow (packet_t pp); static void ig_to_tx_init (void); -static void ig_to_tx_remember (packet_t pp); -static int ig_to_tx_allow (packet_t pp); +static int ig_to_tx_allow (packet_t pp, int chan); /* @@ -256,7 +255,7 @@ int main (int argc, char *argv[]) SLEEP_SEC (20); text_color_set(DW_COLOR_INFO); dw_printf ("Send received packet\n"); - send_msg_to_server ("W1ABC>APRS:?\r\n"); + send_msg_to_server ("W1ABC>APRS:?"); } #endif return 0; @@ -277,6 +276,8 @@ int main (int argc, char *argv[]) static struct audio_s *save_audio_config_p; static struct igate_config_s *save_igate_config_p; static struct digi_config_s *save_digi_config_p; +static int s_debug; + /* * Statistics. @@ -359,6 +360,13 @@ static int stats_rf_xmit_packets; /* Number of packets passed along to radio */ * p_digi_config - Digipeater configuration. * All we care about here is the packet filtering options. * + * debug_level - 0 print packets FROM APRS-IS, + * establishing connection with sergver, and + * and anything rejected by client side filtering. + * 1 plus packets sent TO server or why not. + * 2 plus duplicate detection overview. + * 3 plus duplicate detection details. + * * Description: This starts two threads: * * * to establish and maintain a connection to the server. @@ -367,7 +375,7 @@ static int stats_rf_xmit_packets; /* Number of packets passed along to radio */ *--------------------------------------------------------------------*/ -void igate_init (struct audio_s *p_audio_config, struct igate_config_s *p_igate_config, struct digi_config_s *p_digi_config) +void igate_init (struct audio_s *p_audio_config, struct igate_config_s *p_igate_config, struct digi_config_s *p_digi_config, int debug_level) { #if __WIN32__ HANDLE connnect_th; @@ -377,6 +385,7 @@ void igate_init (struct audio_s *p_audio_config, struct igate_config_s *p_igate_ pthread_t cmd_listen_tid; int e; #endif + s_debug = debug_level; #if DEBUGx text_color_set(DW_COLOR_DEBUG); @@ -738,7 +747,6 @@ static void * connnect_thread (void *arg) strlcat (stemp, " filter ", sizeof(stemp)); strlcat (stemp, save_igate_config_p->t2_filter, sizeof(stemp)); } - strlcat (stemp, "\r\n", sizeof(stemp)); send_msg_to_server (stemp); /* Delay until it is ok to start sending packets. */ @@ -766,7 +774,7 @@ static void * connnect_thread (void *arg) char heartbeat[10]; - strlcpy (heartbeat, "#\r\n", sizeof(heartbeat)); + strlcpy (heartbeat, "#", sizeof(heartbeat)); /* This will close the socket if any error. */ send_msg_to_server (heartbeat); @@ -800,13 +808,15 @@ static void * connnect_thread (void *arg) * *--------------------------------------------------------------------*/ +#define IGATE_MAX_MSG 520 /* Message to IGate max 512 characters. */ + void igate_send_rec_packet (int chan, packet_t recv_pp) { packet_t pp; int n; unsigned char *pinfo; char *p; - char msg[520]; /* Message to IGate max 512 characters. */ + char msg[IGATE_MAX_MSG]; int info_len; @@ -826,11 +836,9 @@ void igate_send_rec_packet (int chan, packet_t recv_pp) if (pfilter(chan, MAX_CHANS, save_digi_config_p->filter_str[chan][MAX_CHANS], recv_pp) != 1) { -// TODO1.2: take out debug message. -//#if DEBUG - text_color_set(DW_COLOR_DEBUG); + text_color_set(DW_COLOR_INFO); dw_printf ("Packet from channel %d to IGate was rejected by filter: %s\n", chan, save_digi_config_p->filter_str[chan][MAX_CHANS]); -//#endif + return; } } @@ -862,19 +870,22 @@ void igate_send_rec_packet (int chan, packet_t recv_pp) strcmp(via, "TCPXX") == 0 || strcmp(via, "RFONLY") == 0 || strcmp(via, "NOGATE") == 0) { -#if DEBUGx - text_color_set(DW_COLOR_DEBUG); - dw_printf ("Rx IGate: Do not relay with TCPIP etc. in path.\n"); -#endif + + if (s_debug >= 1) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Rx IGate: Do not relay with %s in path.\n", via); + } + ax25_delete (pp); return; } } -#if DEBUGx - text_color_set(DW_COLOR_DEBUG); - dw_printf ("Rx IGate: Unwrap third party message.\n"); -#endif + if (s_debug >= 1) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Rx IGate: Unwrap third party message.\n"); + } + inner_pp = ax25_unwrap_third_party(pp); if (inner_pp == NULL) { ax25_delete (pp); @@ -896,10 +907,12 @@ void igate_send_rec_packet (int chan, packet_t recv_pp) strcmp(via, "TCPXX") == 0 || strcmp(via, "RFONLY") == 0 || strcmp(via, "NOGATE") == 0) { -#if DEBUGx - text_color_set(DW_COLOR_DEBUG); - dw_printf ("Rx IGate: Do not relay with TCPIP etc. in path.\n"); -#endif + + if (s_debug >= 1) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Rx IGate: Do not relay with %s in path.\n", via); + } + ax25_delete (pp); return; } @@ -909,10 +922,10 @@ void igate_send_rec_packet (int chan, packet_t recv_pp) * Do not relay generic query. */ if (ax25_get_dti(pp) == '?') { -#if DEBUGx - text_color_set(DW_COLOR_DEBUG); - dw_printf ("Rx IGate: Do not relay generic query.\n"); -#endif + if (s_debug >= 1) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Rx IGate: Do not relay generic query.\n"); + } ax25_delete (pp); return; } @@ -926,18 +939,18 @@ void igate_send_rec_packet (int chan, packet_t recv_pp) (void)(info_len); if ((p = strchr ((char*)pinfo, '\r')) != NULL) { -#if DEBUGx - text_color_set(DW_COLOR_DEBUG); - dw_printf ("Rx IGate: Truncated information part at CR.\n"); -#endif + if (s_debug >= 1) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Rx IGate: Truncated information part at CR.\n"); + } *p = '\0'; } if ((p = strchr ((char*)pinfo, '\n')) != NULL) { -#if DEBUGx - text_color_set(DW_COLOR_DEBUG); - dw_printf ("Rx IGate: Truncated information part at LF.\n"); -#endif + if (s_debug >= 1) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Rx IGate: Truncated information part at LF.\n"); + } *p = '\0'; } @@ -947,10 +960,10 @@ void igate_send_rec_packet (int chan, packet_t recv_pp) */ if (strlen((char*)pinfo) == 0) { -#if DEBUGx - text_color_set(DW_COLOR_DEBUG); - dw_printf ("Rx IGate: Information part length is zero.\n"); -#endif + if (s_debug >= 1) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Rx IGate: Information part length is zero.\n"); + } ax25_delete (pp); return; } @@ -962,10 +975,10 @@ void igate_send_rec_packet (int chan, packet_t recv_pp) */ if ( ! rx_to_ig_allow(pp)) { -#if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("Rx IGate: Drop duplicate of same packet seen recently.\n"); -#endif + if (s_debug >= 1) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Rx IGate: Drop duplicate of same packet seen recently.\n"); + } ax25_delete (pp); return; } @@ -980,7 +993,6 @@ void igate_send_rec_packet (int chan, packet_t recv_pp) strlcat (msg, save_audio_config_p->achan[chan].mycall, sizeof(msg)); strlcat (msg, ":", sizeof(msg)); strlcat (msg, (char*)pinfo, sizeof(msg)); - strlcat (msg, "\r\n", sizeof(msg)); send_msg_to_server (msg); stats_rx_igate_packets++; @@ -1006,7 +1018,7 @@ void igate_send_rec_packet (int chan, packet_t recv_pp) * This one function should be used for login, hearbeats, * and packets. * - * Inputs: msg - Message. Should end with CR/LF. + * Inputs: imsg - Message. We will add CR/LF. * * * Description: Send message to IGate Server if connected. @@ -1015,26 +1027,30 @@ void igate_send_rec_packet (int chan, packet_t recv_pp) *--------------------------------------------------------------------*/ -static void send_msg_to_server (char *msg) +static void send_msg_to_server (const char *imsg) { int err; - + char stemp[IGATE_MAX_MSG]; if (igate_sock == -1) { return; /* Silently discard if not connected. */ } - stats_uplink_bytes += strlen(msg); + strlcpy(stemp, imsg, sizeof(stemp)); -#if DEBUG - text_color_set(DW_COLOR_XMIT); - dw_printf ("[ig] "); - ax25_safe_print (msg, strlen(msg), 0); - dw_printf ("\n"); -#endif + if (s_debug >= 1) { + text_color_set(DW_COLOR_XMIT); + dw_printf ("[rx>ig] "); + ax25_safe_print (stemp, strlen(stemp), 0); + dw_printf ("\n"); + } + + strlcat (stemp, "\r\n", sizeof(stemp)); + + stats_uplink_bytes += strlen(stemp); #if __WIN32__ - err = send (igate_sock, msg, strlen(msg), 0); + err = send (igate_sock, stemp, strlen(stemp), 0); if (err == SOCKET_ERROR) { text_color_set(DW_COLOR_ERROR); @@ -1045,7 +1061,7 @@ static void send_msg_to_server (char *msg) WSACleanup(); } #else - err = write (igate_sock, msg, strlen(msg)); + err = write (igate_sock, stemp, strlen(stemp)); if (err <= 0) { text_color_set(DW_COLOR_ERROR); @@ -1169,7 +1185,31 @@ static void * igate_recv_thread (void *arg) /* * We have a complete message terminated by LF. + * + * Remove CR LF from end. + * This is a record separator for the protocol, not part of the data. + * Should probably have an error if we don't have this. */ + if (len >=2 && message[len-1] == '\n') { message[len-1] = '\0'; len--; } + if (len >=1 && message[len-1] == '\r') { message[len-1] = '\0'; len--; } + +/* + * I've seen a case where the original RF packet had a trailing CR but + * after someone else sent it to the server and it came back to me, that + * CR was now a trailing space. + * At first I was tempted to trim a trailing space as well. + * By fixing this one case it might corrupt the data in other cases. + * We compensate for this by ignoring trailing spaces when performing + * the duplicate detection and removal. + */ + +/* + * I've also seen a multiple trailing spaces like this. + * Notice how safe_print shows a trailing space in hexadecimal to make it obvious. + * + * W1CLA-1>APVR30,TCPIP*,qAC,T2TOKYO3:;IRLP-4942*141503z4218.46NI07108.24W0446325-146IDLE <0x20> + */ + if (len == 0) { /* @@ -1184,35 +1224,33 @@ static void * igate_recv_thread (void *arg) * That way we can see login confirmation but not * be bothered by the heart beat messages. */ -#ifndef DEBUG + if ( ! ok_to_send) { -#endif text_color_set(DW_COLOR_REC); dw_printf ("[ig] "); ax25_safe_print ((char *)message, len, 0); dw_printf ("\n"); -#ifndef DEBUG } -#endif } else { /* * Convert to third party packet and transmit. + * + * Future: might have ability to configure multiple transmit + * channels, each with own client side filtering and via path. + * Loop here over all configured channels. */ - text_color_set(DW_COLOR_REC); - dw_printf ("\n[ig] "); + dw_printf ("\n[ig>tx] "); // formerly just [ig] ax25_safe_print ((char *)message, len, 0); dw_printf ("\n"); -/* - * Remove CR LF from end. - */ - if (len >=2 && message[len-1] == '\n') { message[len-1] = '\0'; len--; } - if (len >=1 && message[len-1] == '\r') { message[len-1] = '\0'; len--; } + int to_chan = save_igate_config_p->tx_chan; - xmit_packet ((char*)message); + if (to_chan >= 0) { + xmit_packet ((char*)message, to_chan); + } } } /* while (1) */ @@ -1229,31 +1267,43 @@ static void * igate_recv_thread (void *arg) * packet and send to transmit queue. * * Inputs: message - As sent by the server. + * Any trailing CRLF should have been removed. + * Typical examples: + * + * KA1BTK-5>APDR13,TCPIP*,qAC,T2IRELAND:=4237.62N/07040.68W$/A=-00054 http://aprsdroid.org/ + * N1HKO-10>APJI40,TCPIP*,qAC,N1HKO-JS:APWW10,WIDE1-1,WIDE2-1,qAS,K1RI:/221700h/9AmAT3PQ3S,WIDE1-1,WIDE2-1,qAR,W1TG-1:`c)@qh\>/"50}TinyTrak4 Mobile + * + * Notice how the final address in the header might not + * be a valid AX.25 address. We see a 9 character address + * (with no ssid) and an ssid of two letters. + * We don't care because we end up discarding them before + * repackaging to go over the radio. + * + * The "q construct" ( http://www.aprs-is.net/q.aspx ) provides + * a clue about the journey taken but I don't think we care here. + * + * to_chan - Radio channel for transmitting. * *--------------------------------------------------------------------*/ -static void xmit_packet (char *message) +static void xmit_packet (char *message, int to_chan) { packet_t pp3; char payload[AX25_MAX_PACKET_LEN]; /* what is max len? */ char *pinfo = NULL; int info_len; - int to_chan = save_igate_config_p->tx_chan; /* Should be -1 if not configured for xmit!!! */ - /* Future: Array of boolean to allow multiple xmit channels? */ - -/* - * Is IGate to Radio direction enabled? - */ - if (to_chan == -1) { - return; - } - - stats_tx_igate_packets++; assert (to_chan >= 0 && to_chan < MAX_CHANS); + /* * Try to parse it into a packet object. + * This will contain "q constructs" and we might see an address + * with two alphnumeric characters in the SSID so we must use + * the non-strict parsing. + * * Bug: Up to 8 digipeaters are allowed in radio format. * There is a potential of finding a larger number here. */ @@ -1268,6 +1318,9 @@ static void xmit_packet (char *message) /* * Apply our own packet filtering if configured. + * Do we want to do this before or after removing the VIA path? + * I suppose by doing it first, we have the possibility of + * filtering by stations along the way or the q construct. */ assert (to_chan >= 0 && to_chan < MAX_CHANS); @@ -1276,35 +1329,37 @@ static void xmit_packet (char *message) if (pfilter(MAX_CHANS, to_chan, save_digi_config_p->filter_str[MAX_CHANS][to_chan], pp3) != 1) { -// TODO1.2: take out debug message. One person liked it as a confirmation of what was going on. -// Maybe it should be part of a more comprehensive debug facility? -//#if DEBUG - text_color_set(DW_COLOR_DEBUG); + text_color_set(DW_COLOR_INFO); dw_printf ("Packet from IGate to channel %d was rejected by filter: %s\n", to_chan, save_digi_config_p->filter_str[MAX_CHANS][to_chan]); -//#endif + ax25_delete (pp3); return; } } -/* - * TODO: Discard if qAX in path??? others? - */ /* * Remove the VIA path. + * + * For example, we might get something like this from the server. + * K1USN-1>APWW10,TCPIP*,qAC,N5JXS-F1:T#479,100,048,002,500,000,10000000<0x0d><0x0a> + * + * We want to reduce it to this before wrapping it as third party traffic. + * K1USN-1>APWW10:T#479,100,048,002,500,000,10000000<0x0d><0x0a> */ + while (ax25_get_num_repeaters(pp3) > 0) { ax25_remove_addr (pp3, AX25_REPEATER_1); } + /* * Replace the VIA path with TCPIP and my call. * Mark my call as having been used. */ ax25_set_addr (pp3, AX25_REPEATER_1, "TCPIP"); ax25_set_h (pp3, AX25_REPEATER_1); - ax25_set_addr (pp3, AX25_REPEATER_2, save_audio_config_p->achan[save_igate_config_p->tx_chan].mycall); + ax25_set_addr (pp3, AX25_REPEATER_2, save_audio_config_p->achan[to_chan].mycall); ax25_set_h (pp3, AX25_REPEATER_2); /* @@ -1324,12 +1379,12 @@ static void xmit_packet (char *message) /* * Encapsulate for sending over radio if no reason to drop it. */ - if (ig_to_tx_allow (pp3)) { + if (ig_to_tx_allow (pp3, to_chan)) { char radio [500]; packet_t pradio; snprintf (radio, sizeof(radio), "%s>%s%d%d%s:}%s", - save_audio_config_p->achan[save_igate_config_p->tx_chan].mycall, + save_audio_config_p->achan[to_chan].mycall, APP_TOCALL, MAJOR_VERSION, MINOR_VERSION, save_igate_config_p->tx_via, payload); @@ -1341,16 +1396,18 @@ static void xmit_packet (char *message) if (pradio != NULL) { + stats_tx_igate_packets++; + #if ITEST text_color_set(DW_COLOR_XMIT); dw_printf ("Xmit: %s\n", radio); ax25_delete (pradio); #else /* This consumes packet so don't reference it again! */ - tq_append (save_igate_config_p->tx_chan, TQ_PRIO_1_LO, pradio); + tq_append (to_chan, TQ_PRIO_1_LO, pradio); #endif stats_rf_xmit_packets++; - ig_to_tx_remember (pp3); + ig_to_tx_remember (pp3, save_igate_config_p->tx_chan, 0); // correct. version before encapsulating it. } else { text_color_set(DW_COLOR_ERROR); @@ -1381,7 +1438,8 @@ static void xmit_packet (char *message) * * Name: rx_to_ig_allow * - * Purpose: Check whether this is a duplicate of another sent recently. + * Purpose: Check whether this is a duplicate of another + * recently received from RF and sent to the Server * * Input: pp - Pointer to packet object. * @@ -1427,9 +1485,28 @@ static void rx_to_ig_init (void) static void rx_to_ig_remember (packet_t pp) { + rx2ig_time_stamp[rx2ig_insert_next] = time(NULL); rx2ig_checksum[rx2ig_insert_next] = ax25_dedupe_crc(pp); + if (s_debug >= 3) { + char src[AX25_MAX_ADDR_LEN]; + char dest[AX25_MAX_ADDR_LEN]; + unsigned char *pinfo; + int info_len; + + ax25_get_addr_with_ssid(pp, AX25_SOURCE, src); + ax25_get_addr_with_ssid(pp, AX25_DESTINATION, dest); + info_len = ax25_get_info (pp, &pinfo); + + text_color_set(DW_COLOR_DEBUG); + dw_printf ("rx_to_ig_remember [%d] = %d %d \"%s>%s:%s\"\n", + rx2ig_insert_next, + (int)(rx2ig_time_stamp[rx2ig_insert_next]), + rx2ig_checksum[rx2ig_insert_next], + src, dest, pinfo); + } + rx2ig_insert_next++; if (rx2ig_insert_next >= RX2IG_HISTORY_MAX) { rx2ig_insert_next = 0; @@ -1442,11 +1519,35 @@ static int rx_to_ig_allow (packet_t pp) time_t now = time(NULL); int j; + if (s_debug >= 2) { + char src[AX25_MAX_ADDR_LEN]; + char dest[AX25_MAX_ADDR_LEN]; + unsigned char *pinfo; + int info_len; + + ax25_get_addr_with_ssid(pp, AX25_SOURCE, src); + ax25_get_addr_with_ssid(pp, AX25_DESTINATION, dest); + info_len = ax25_get_info (pp, &pinfo); + + text_color_set(DW_COLOR_DEBUG); + dw_printf ("rx_to_ig_allow? %d \"%s>%s:%s\"\n", crc, src, dest, pinfo); + } + for (j=0; j= now - RX2IG_DEDUPE_TIME && rx2ig_checksum[j] == crc) { + if (rx2ig_checksum[j] == crc && rx2ig_time_stamp[j] >= now - RX2IG_DEDUPE_TIME) { + if (s_debug >= 2) { + text_color_set(DW_COLOR_DEBUG); + // could be multiple entries and this might not be the most recent. + dw_printf ("rx_to_ig_allow? NO. Seen %d seconds ago.\n", (int)(now - rx2ig_time_stamp[j])); + } return 0; } } + + if (s_debug >= 2) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("rx_to_ig_allow? YES\n"); + } return 1; } /* end rx_to_ig_allow */ @@ -1462,6 +1563,12 @@ static int rx_to_ig_allow (packet_t pp) * * Inputs: pp - Pointer to a packet object. * + * chan - Channel number where it is being transmitted. + * Duplicate detection needs to be separate for each radio channel. + * + * bydigi - True if transmitted by digipeater function. False for IGate. + * Why do we care about digpeating here? See discussion below. + * *------------------------------------------------------------------------------ * * Name: ig_to_tx_allow @@ -1470,6 +1577,8 @@ static int rx_to_ig_allow (packet_t pp) * or if we exceed the transmit rate limits. * * Input: pp - Pointer to packet object. + * + * chan - Radio channel number where we want to transmit. * * Returns: True if it is OK to send. * @@ -1488,8 +1597,8 @@ static int rx_to_ig_allow (packet_t pp) * This is the essentially the same as the pair of functions * above with one addition restriction. * - * The typical residential Internet connection is about 10,000 - * times faster than the radio links we are using. It would + * The typical residential Internet connection is around 10,000 + * to 50,000 times faster than the radio links we are using. It would * be easy to completely saturate the radio channel if we are * not careful. * @@ -1497,26 +1606,155 @@ static int rx_to_ig_allow (packet_t pp) * number of packets sent during the past minute and past 5 * minutes and stop sending if a limit is reached. * - * Future? We might also want to avoid transmitting if the same packet - * was heard on the radio recently. If everything is kept in - * the same table, we'd need to distinguish between those from - * the IGate server and those heard on the radio. - * Those heard on the radio would not count toward the - * 1 and 5 minute rate limiting. - * Maybe even provide informative information such as - - * Tx IGate: Same packet heard recently from W1ABC and W9XYZ. + * More Discussion: * - * Of course, the radio encapsulation would need to be removed - * and only the 3rd party packet inside compared. + * Consider the following example. + * I hear a packet from W1TG-1 three times over the radio then get the + * (almost) same thing twice from APRS-IS. * + * + * Digipeater N3LEE-10 audio level = 23(10/6) [NONE] __||||||| + * [0.5] W1TG-1>APU25N,N3LEE-10*,WIDE2-1: + * Station Capabilities, Ambulance, UIview 32 bit apps + * IGATE,MSG_CNT=30,LOC_CNT=61 + * + * [0H] W1TG-1>APU25N,N3LEE-10,WB2OSZ-14*: + * + * Digipeater WIDE2 (probably N3LEE-4) audio level = 22(10/6) [NONE] __||||||| + * [0.5] W1TG-1>APU25N,N3LEE-10,N3LEE-4,WIDE2*: + * Station Capabilities, Ambulance, UIview 32 bit apps + * IGATE,MSG_CNT=30,LOC_CNT=61 + * + * Digipeater WIDE2 (probably AB1OC-10) audio level = 31(14/11) [SINGLE] ____:____ + * [0.4] W1TG-1>APU25N,N3LEE-10,AB1OC-10,WIDE2*: + * Station Capabilities, Ambulance, UIview 32 bit apps + * IGATE,MSG_CNT=30,LOC_CNT=61 + * + * [ig] W1TG-1>APU25N,WIDE2-2,qAR,W1GLO-11:APDW13,WIDE1-1:}W1TG-1>APU25N,TCPIP,WB2OSZ-14*:APU25N,K1FFK,WIDE2*,qAR,WB2ZII-15: + * [0L] WB2OSZ-14>APDW13,WIDE1-1:}W1TG-1>APU25N,TCPIP,WB2OSZ-14*: + * + * + * The first one gets retransmitted by digipeating. + * + * Why are we getting the same thing twice from APRS-IS? Shouldn't remove duplicates? + * Look closely. The original packet, on RF, had a CR character at the end. + * At first I thought duplicate removal was broken but it turns out they + * are not exactly the same. + * + * The receive IGate spec says a packet should be cut at a CR. + * In one case it is removed as expected In another case, it is replaced by a trailing + * space character. Maybe someone thought non printable characters should be + * replaced by spaces??? + * + * At first I was tempted to remove any trailing spaces to make up for the other + * IGate adding it. Two wrongs don't make a right. Trailing spaces are not that + * rare and removing them would corrupt the data. My new strategy is for + * the duplicate detection compare to ignore trailing space, CR, and LF. + * + * We already transmitted the same thing by the digipeater function so this should + * also go into memory for avoiding duplicates out of the transmit IGate. + * + * Future: + * Should the digipeater function avoid transmitting something if it + * was recently transmitted by the IGate funtion? + * This code is pretty much the same as dedupe.c. Maybe it could all + * be combined into one. Need to ponder this some more. + * *--------------------------------------------------------------------*/ +/* +Here is another complete example, with the "-diii" debugging option to show details. + + +We receive the signal directly from the source: (zzz.log 1011) + + N1ZKO-7 audio level = 33(16/10) [NONE] ___|||||| + [0.5] N1ZKO-7>T2TS7X,WIDE1-1,WIDE2-1:`c6wl!i[/>"4]}[scanning]=<0x0d> + MIC-E, Human, Kenwood TH-D72, In Service + N 42 43.7800, W 071 26.9100, 0 MPH, course 177, alt 230 ft + [scanning] + +We did not send it to the IS server recently. + + Rx IGate: Truncated information part at CR. + rx_to_ig_allow? 57185 "N1ZKO-7>T2TS7X:`c6wl!i[/>"4]}[scanning]=" + rx_to_ig_allow? YES + +Send it now and remember that fact. + + [rx>ig] N1ZKO-7>T2TS7X,WIDE1-1,WIDE2-1,qAR,WB2OSZ-14:`c6wl!i[/>"4]}[scanning]= + rx_to_ig_remember [21] = 1447683040 57185 "N1ZKO-7>T2TS7X:`c6wl!i[/>"4]}[scanning]=" + +Digipeat it. Notice how it has a trailing CR. +TODO: Why is the CRC different? Content looks the same. + + ig_to_tx_remember [38] = ch0 d1 1447683040 27598 "N1ZKO-7>T2TS7X:`c6wl!i[/>"4]}[scanning]= " + [0H] N1ZKO-7>T2TS7X,WB2OSZ-14*,WIDE2-1:`c6wl!i[/>"4]}[scanning]=<0x0d> + +Now we hear it again, thru a digipeater. +Not sure who. Was it UNCAN or was it someone else who doesn't use tracing? +See my rant in the User Guide about this. + + Digipeater WIDE2 (probably UNCAN) audio level = 30(15/10) [NONE] __|||::__ + [0.4] N1ZKO-7>T2TS7X,KB1POR-2,UNCAN,WIDE2*:`c6wl!i[/>"4]}[scanning]=<0x0d> + MIC-E, Human, Kenwood TH-D72, In Service + N 42 43.7800, W 071 26.9100, 0 MPH, course 177, alt 230 ft + [scanning] + +Was sent to server recently so don't do it again. + + Rx IGate: Truncated information part at CR. + rx_to_ig_allow? 57185 "N1ZKO-7>T2TS7X:`c6wl!i[/>"4]}[scanning]=" + rx_to_ig_allow? NO. Seen 1 seconds ago. + Rx IGate: Drop duplicate of same packet seen recently. + +We hear it a third time, by a different digipeater. + + Digipeater WIDE1 (probably N3LEE-10) audio level = 23(12/6) [NONE] __||||||| + [0.5] N1ZKO-7>T2TS7X,N3LEE-10,WIDE1*,WIDE2-1:`c6wl!i[/>"4]}[scanning]=<0x0d> + MIC-E, Human, Kenwood TH-D72, In Service + N 42 43.7800, W 071 26.9100, 0 MPH, course 177, alt 230 ft + [scanning] + +It's a duplicate, so don't send to server. + + Rx IGate: Truncated information part at CR. + rx_to_ig_allow? 57185 "N1ZKO-7>T2TS7X:`c6wl!i[/>"4]}[scanning]=" + rx_to_ig_allow? NO. Seen 2 seconds ago. + Rx IGate: Drop duplicate of same packet seen recently. + Digipeater: Drop redundant packet to channel 0. + +The server sends it to us. +NOTICE: The CR at the end has been replaced by a space. + + [ig>tx] N1ZKO-7>T2TS7X,K1FFK,WA2MJM-15*,qAR,WB2ZII-15:`c6wl!i[/>"4]}[scanning]=<0x20> + +Should we transmit it? +No, we sent it recently by the digipeating function (note "bydigi=1"). + + DEBUG: ax25_dedupe_crc ignoring trailing space. + ig_to_tx_allow? ch0 27598 "N1ZKO-7>T2TS7X:`c6wl!i[/>"4]}[scanning]= " + ig_to_tx_allow? NO. Sent 4 seconds ago. bydigi=1 + Tx IGate: Drop duplicate packet transmitted recently. + [0L] WB2OSZ-14>APDW13,WIDE1-1:}W1AST>TRPR4T,TCPIP,WB2OSZ-14*:`d=Ml!3>/"4N} + [rx>ig] # +*/ + + #define IG2TX_DEDUPE_TIME 60 /* Do not send duplicate within 60 seconds. */ #define IG2TX_HISTORY_MAX 50 /* Remember the last 50 sent from server to radio. */ +/* Ideally this should be a critical region because */ +/* it is being written by two threads but I'm not that concerned. */ + static int ig2tx_insert_next; static time_t ig2tx_time_stamp[IG2TX_HISTORY_MAX]; static unsigned short ig2tx_checksum[IG2TX_HISTORY_MAX]; +static unsigned char ig2tx_chan[IG2TX_HISTORY_MAX]; +static unsigned short ig2tx_bydigi[IG2TX_HISTORY_MAX]; static void ig_to_tx_init (void) { @@ -1524,15 +1762,40 @@ static void ig_to_tx_init (void) for (n=0; n= 3) { + char src[AX25_MAX_ADDR_LEN]; + char dest[AX25_MAX_ADDR_LEN]; + unsigned char *pinfo; + int info_len; + + ax25_get_addr_with_ssid(pp, AX25_SOURCE, src); + ax25_get_addr_with_ssid(pp, AX25_DESTINATION, dest); + info_len = ax25_get_info (pp, &pinfo); + + text_color_set(DW_COLOR_DEBUG); + dw_printf ("ig_to_tx_remember [%d] = ch%d d%d %d %d \"%s>%s:%s\"\n", + ig2tx_insert_next, + chan, bydigi, + (int)(now), crc, + src, dest, pinfo); + } + + ig2tx_time_stamp[ig2tx_insert_next] = now; + ig2tx_checksum[ig2tx_insert_next] = crc; + ig2tx_chan[ig2tx_insert_next] = chan; + ig2tx_bydigi[ig2tx_insert_next] = bydigi; ig2tx_insert_next++; if (ig2tx_insert_next >= IG2TX_HISTORY_MAX) { @@ -1540,25 +1803,51 @@ static void ig_to_tx_remember (packet_t pp) } } -static int ig_to_tx_allow (packet_t pp) +static int ig_to_tx_allow (packet_t pp, int chan) { unsigned short crc = ax25_dedupe_crc(pp); time_t now = time(NULL); int j; int count_1, count_5; + if (s_debug >= 2) { + char src[AX25_MAX_ADDR_LEN]; + char dest[AX25_MAX_ADDR_LEN]; + unsigned char *pinfo; + int info_len; + + ax25_get_addr_with_ssid(pp, AX25_SOURCE, src); + ax25_get_addr_with_ssid(pp, AX25_DESTINATION, dest); + info_len = ax25_get_info (pp, &pinfo); + + text_color_set(DW_COLOR_DEBUG); + dw_printf ("ig_to_tx_allow? ch%d %d \"%s>%s:%s\"\n", chan, crc, src, dest, pinfo); + } + + /* Consider transmissions on this channel only by either digi or IGate. */ + for (j=0; j= now - IG2TX_DEDUPE_TIME && ig2tx_checksum[j] == crc) { + if (ig2tx_checksum[j] == crc && ig2tx_chan[j] == chan && ig2tx_time_stamp[j] >= now - IG2TX_DEDUPE_TIME) { + if (s_debug >= 2) { + text_color_set(DW_COLOR_DEBUG); + // could be multiple entries and this might not be the most recent. + dw_printf ("ig_to_tx_allow? NO. Sent %d seconds ago. bydigi=%d\n", (int)(now - ig2tx_time_stamp[j]), ig2tx_bydigi[j]); + } text_color_set(DW_COLOR_INFO); dw_printf ("Tx IGate: Drop duplicate packet transmitted recently.\n"); return 0; } } + + /* IGate transmit counts must not include digipeater transmissions. */ + count_1 = 0; count_5 = 0; for (j=0; j= now - 60) count_1++; - if (ig2tx_time_stamp[j] >= now - 300) count_5++; + if (ig2tx_chan[j] == chan && ig2tx_bydigi[j] == 0) { + if (ig2tx_time_stamp[j] >= now - 60) count_1++; + if (ig2tx_time_stamp[j] >= now - 300) count_5++; + } } if (count_1 >= save_igate_config_p->tx_limit_1) { @@ -1572,6 +1861,11 @@ static int ig_to_tx_allow (packet_t pp) return 0; } + if (s_debug >= 2) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("ig_to_tx_allow? YES\n"); + } + return 1; } /* end ig_to_tx_allow */ diff --git a/igate.h b/igate.h index 58f2649..1736d04 100644 --- a/igate.h +++ b/igate.h @@ -55,13 +55,24 @@ struct igate_config_s { int tx_limit_5; /* Max. packets to transmit in 5 minutes. */ }; + +#define IGATE_TX_LIMIT_1_DEFAULT 6 +#define IGATE_TX_LIMIT_1_MAX 20 + +#define IGATE_TX_LIMIT_5_DEFAULT 20 +#define IGATE_TX_LIMIT_5_MAX 80 + + /* Call this once at startup */ -void igate_init (struct audio_s *p_audio_config, struct igate_config_s *p_igate_config, struct digi_config_s *p_digi_config); +void igate_init (struct audio_s *p_audio_config, struct igate_config_s *p_igate_config, struct digi_config_s *p_digi_config, int debug_level); /* Call this with each packet received from the radio. */ void igate_send_rec_packet (int chan, packet_t recv_pp); +/* This when digipeater transmits. Set bydigi to 1 . */ + +void ig_to_tx_remember (packet_t pp, int chan, int bydigi); #endif diff --git a/man1/direwolf.1 b/man1/direwolf.1 index b78f70a..8f9acc7 100644 --- a/man1/direwolf.1 +++ b/man1/direwolf.1 @@ -82,6 +82,8 @@ g = GPS interface. t = Tracker beacon. .P o = Output controls such as PTT and DCD. +.P +i = IGate .RE .RE .PD diff --git a/xmit.c b/xmit.c index bfacfcb..21d4b67 100644 --- a/xmit.c +++ b/xmit.c @@ -289,7 +289,6 @@ void xmit_init (struct audio_s *p_modem, int debug_xmit_packet) - /*------------------------------------------------------------------- * * Name: xmit_set_txdelay @@ -606,6 +605,7 @@ static void xmit_ax25_frames (int c, int p, packet_t pp) dw_printf ("%s", stemp); /* stations followed by : */ ax25_safe_print ((char *)pinfo, info_len, ! ax25_is_aprs(pp)); dw_printf ("\n"); + (void)ax25_check_addresses (pp); /* Optional hex dump of packet. */ @@ -669,6 +669,7 @@ static void xmit_ax25_frames (int c, int p, packet_t pp) dw_printf ("%s", stemp); /* stations followed by : */ ax25_safe_print ((char *)pinfo, info_len, ! ax25_is_aprs(pp)); dw_printf ("\n"); + (void)ax25_check_addresses (pp); if (g_debug_xmit_packet) { text_color_set(DW_COLOR_DEBUG);