perl-based GPIO control for pine64

The pine64, like many single board computers, has a file system interface for its gpio pins.  This means that reading and writing logic levels on a particular gpio pin is a matter of reading and writing to the particular file for that pin.  Interacting with files in this manner is something that perl excels at.  That is one reason why I mostly forego using ready made tools like wiringpi and make my own gpio interface functions.  This also help me to understand the system more completely, and customize according to the way that I think, instead of using another’s canned tools.

I have written a number of perl scripts for the raspberry pi that control the gpio system interface and bit-banged drivers for several popular integrated circuits such as the MCP3004, MCP3008, MCP3208 (all analog to digital converters) , MAX7219, 74HC595, and others.  The pine64 has a (mostly) compatible 40-pin gpio header.  I only had to change one line of my gpio driver to make it work with the pine64.  Here is the perl gpio function file in it’s entirety:

 

#!/usr/bin/perl -w
use strict;

=pod
gpio utilities for pine64
enable, disable, read, write
=cut

#global vars
#array for gpio pin numbers
my @line_nums = (227,226,362,71,233,76,64,65,66,229,230,69,73,80,32,33,72,77,78,79,67,231,68,70,74,75);
my ($iox, $status);

sub gpio_enable{
	my $ind = $_[0];
	$iox = $line_nums[$ind];
	#print "IO line: $iox\n";

	my $gpiosz = @line_nums;	
	#print "gpiosz: $gpiosz\n";

	my $direction = $_[1];
	#print "direction: $direction\n";

	#err chk
	if($ind < 0 || $ind > $gpiosz){
		print "INVALID GPIO RANGE\n";
		exit;
	}#end if invalid index
	if($direction ne 'in' && $direction ne 'out'){
		print "INVALID DIRECTION\n";
		exit;
	}#end if invalid direction
	
	#write gpio pin val to export file
	open(EF, ">", "/sys/class/gpio/export") or die $!;
	print EF $iox;
	close(EF);

	#set direction of gpio pin
	open(GF, ">", "/sys/class/gpio/gpio$iox/direction") or die $!;
	print GF $direction;
	close(GF);

}#end gpio_enable

sub gpio_read{
	#gpio number as arg, returns direction in/out
	my $ind = $_[0];
	$iox = $line_nums[$ind];
	
	my $value = ''; 

	#reads state of gpio pin
	open(GS, "/sys/class/gpio/gpio$iox/value") or die $!;
	while(){ $value = $_; };
	#print "$iox val: $value";
	close(GS);

	if($value == 0 || $value == 1){
		return $value;
	}#end unless
	else{ 
		print "ERROR: Undefined value on GPIO $iox\n";
		exit;
	}#end else
}#end gpio_read

sub gpio_write{
	my $ind = $_[0];
	$iox = $line_nums[$ind];

	my $value = $_[1];

	#write value to gpio pin
	open(GV, ">", "/sys/class/gpio/gpio$iox/value") or die $!;
	print GV $value;
	close(GV);
}#end gpio_write

1;

Very simple (under 80 lines) and very effective.  This has been tested ad infinitum to work as expected.  This gpio function file is also the main building block for my bit-banged drivers.    To demonstrate,  here is an example using a MAX7219 64-LED display driver.  I wrote a custom bit-banged driver using this gpio script.

pine64 perl max7219 driver
pine64 MAX7219 perl based driver

When I write a driver like this, I get the data sheet from the manufacturer, and write functions to set all of the configuration registers and control the IO functions.  I use these 8-position, 7-segment displays frequently in my home brew projects, so I started there with my new pine64.  The driver I wrote for this particular display has some visual effects,  sets the intensity, and has some easy to use functions that can read text files word by word.  I can also handle cascaded displays and a few more novel features that I have not seen anywhere else.  Here is the display driver in its current entirety:

 

#!/usr/bin/perl 
use strict;
use Time::HiRes qw (usleep);
require "/home/debian/gpio/gpio_pine64.pl";

=pod
routines to control a MAX7219 8-digit LED
display driver.  
for now:
CLK: gpio 0
DS : gpio 1
LOAD:gpio 2
data is shifted into regs on rising edge of CLK, 
output displayed on rising edge of LOAD

NOTES:
This version supports cascading several 7219's.  I have
noticed that even with programs that assume only one
7219, that if there is another cascaded, the exact same
output is mirrored on subsequent displays. 

I assume that turn_on, set_intenisty, set_scanlimit, etc
all get shifted into the next chip anyway, however, 
these subroutines take the number of cascaded 7219s as
an argument and shift in and latch the necessary
controls to use the display.  
=cut

#global vars for gpio init function
#init should be called from script
#init7219 (0,1,2);
my ($clk, $ds, $load);

#SUBROUTINE PROTOTYPES
=pod

=cut

############### GLOBAL VARS #####################
#shutdown register 
my @sdreg = (0,0,0,0,1,1,0,0);
my @turn_on = (0,0,0,0,0,0,0,1);
my @turn_off = (0,0,0,0,0,0,0,0);

#set scan limit register 
my @slimreg = (0,0,0,0,1,0,1,1);
#last 3 LSBs scan all 8 digits
my @slregall = (0,0,0,0,0,1,1,1);

#display test register
my @disptstreg = (0,0,0,0,1,1,1,1);

#intensity register
my @intreg = (0,0,0,0,1,0,1,0);

#digit addrs
#first 4 bits D15-D12
#don't care bits, then addr
my @_d0 = (0,1,0,1,0,0,0,1);
my @_d1 = (0,1,0,1,0,0,1,0);
my @_d2 = (0,1,0,1,0,0,1,1);
my @_d3 = (0,1,0,1,0,1,0,0);
my @_d4 = (0,1,0,1,0,1,0,1);
my @_d5 = (0,1,0,1,0,1,1,0);
my @_d6 = (0,1,0,1,0,1,1,1);
my @_d7 = (0,1,0,1,1,0,0,0);

#0-9
my @_1 = (0,0,1,1,0,0,0,0);
my @_2 = (0,1,1,0,1,1,0,1);
my @_3 = (0,1,1,1,1,0,0,1);
my @_4 = (0,0,1,1,0,0,1,1);
my @_5 = (0,1,0,1,1,0,1,1);
my @_6 = (0,1,0,1,1,1,1,1);
my @_7 = (0,1,1,1,0,0,0,0);
my @_8 = (0,1,1,1,1,1,1,1);
my @_9 = (0,1,1,1,0,0,1,1);
my @_0 = (0,1,1,1,1,1,1,0);
my @_all = (1,1,1,1,1,1,1,1);

#alpha chars
my @_a = (0,1,1,1,0,1,1,1);
my @_b = (0,0,0,1,1,1,1,1);
my @_c = (0,1,0,0,1,1,1,0);
my @_d = (0,0,1,1,1,1,0,1);
my @_e = (0,1,0,0,1,1,1,1);
my @_f = (0,1,0,0,0,1,1,1);
my @_g = (0,1,1,1,1,0,1,1);
my @_h = (0,0,1,1,0,1,1,1);
my @_i = (0,0,0,0,0,1,0,0);
my @_j = (0,0,1,1,1,1,0,0);
my @_k = (0,0,1,1,0,1,1,0);
my @_l = (0,0,0,0,1,1,1,0);
my @_m = (1,0,0,1,0,1,0,0);
my @_n = (0,0,0,1,0,1,0,1);
my @_o = (0,0,0,1,1,1,0,1);
my @_p = (0,1,1,0,0,1,1,1);
my @_q = (0,1,1,1,0,0,1,1);
my @_r = (0,0,0,0,0,1,0,1);
my @_s = (0,1,0,1,1,0,1,1);
my @_t = (0,0,0,0,1,1,1,1);
my @_u = (0,0,0,1,1,1,0,0);
my @_v = (0,0,0,1,1,1,0,0);
my @_w = (0,0,0,1,0,1,0,0);
my @_x = (0,0,0,1,1,1,0,1);
my @_y = (0,0,1,1,1,0,1,1);
my @_z = (0,1,1,0,1,1,0,1);

#special chars
my @_dp = (1,0,0,0,0,0,0,0);
my @_sp = (0,0,0,0,0,0,0,0);
my @_dash = (0,0,0,0,0,0,0,1);
my @_comma = (1,0,0,0,0,0,0,0);
my @_period = (1,0,0,0,0,0,0,0);
my @_question = (1,1,1,0,0,0,1,0);
my @_exclaimation = (1,0,1,1,0,0,0,0);

#individual segments
my @_seg_a = (0,1,0,0,0,0,0,0);
my @_seg_b = (0,0,1,0,0,0,0,0);
my @_seg_c = (0,0,0,1,0,0,0,0);
my @_seg_d = (0,0,0,0,1,0,0,0);
my @_seg_e = (0,0,0,0,0,1,0,0);
my @_seg_f = (0,0,0,0,0,0,1,0);
my @_seg_g = (0,0,0,0,0,0,0,1);

#@all_digits is an arra of array references
#representing each digit used to operate
#on each digit 
my @all_digits = (\@_d0,\@_d1,\@_d2,\@_d3,\@_d4,\@_d5,\@_d6,\@_d7);

#@all_segs is an array of array references
#representing each segment of a 7-seg LED array
my @all_segs = (\@_seg_a,\@_seg_b,\@_seg_c,\@_seg_d,\@_seg_e,\@_seg_f,\@_seg_g);

#Hash that maps alphanumeric chars to array ref
my %alphanums = (
	'A' => \@_a,
	'B' => \@_b,
	'C' => \@_c,
	'D' => \@_d,
	'E' => \@_e,
	'F' => \@_f,
	'G' => \@_g,
	'H' => \@_h,
	'I' => \@_i,
	'J' => \@_j,
	'K' => \@_k,
	'L' => \@_l,
	'M' => \@_m,
	'N' => \@_n,
	'O' => \@_o,
	'P' => \@_p,
	'Q' => \@_q,
	'R' => \@_r,
	'S' => \@_s,
	'T' => \@_t,
	'U' => \@_u,
	'V' => \@_v,
	'W' => \@_w,
	'X' => \@_x,
	'Y' => \@_y,
	'Z' => \@_z, 
	'0', => \@_0, 
	'1', => \@_1, 
	'2', => \@_2, 
	'3', => \@_3, 
	'4', => \@_4, 
	'5', => \@_5, 
	'6', => \@_6, 
	'7', => \@_7, 
	'8', => \@_8, 
	'9', => \@_9, 
	'.' => \@_period, 
	'-' => \@_dash, 
	',' => \@_comma, 
	'?' => \@_question, 
	'!' => \@_exclaimation
);#end %alphanums declaration

############### SUBROUTINES #####################
sub shift_in{
=pod
data is shifted in with 16 bit packets: 8-bit segment,
4-bit digit address, 4-bit dont care bits
=cut
	#array ref seg data
	my $leds = $_[0];
	#array ref seg addr
	my $addr = $_[1];
	#number cascaded 7219s
	my $ncas = $_[2];
	#delay in milliseconds
	my $delay = $_[3];
	#latch flag
	my $lf = $_[4];

	#high flag for data gpio line
	my $hf = 0;

	#my $ncp = $ncas * 32;	#min number 32 for 16 clock pulses
	my $ncp = 32;
	##print "ncp: $ncp\n"; 

	#main clock loop
	my $i=0;
	#$state toggles from 1 to 0 needed for clock pulse
	my $state = 0;					
	#ensures first pulse goes from low to high
	my $seed = 3;					

	while($i<$ncp){#correct num clock pulses $state = $seed%2; $seed++; #load data in last 8 clock pulses #make DS high before clock pulse; only on even num #so index array is whole number #MSB read first, then addr, last data if(($i%2) eq 0){ #address D8-D11, and don't care bits D12-D15 if($addr->[$i/2] eq 1 && $i<=14){ gpio_write($ds,1); $hf = 1;#set high flag }#end addr high bit #7-seg data, D0-D7 if($leds->[($i-16)/2] eq 1 && $i > 14){#array ref, light this led up
				#test; set q1 high
				##print "inside data loop, i: $i\tindex: " .(($i-16)/2) . "\n";
				gpio_write($ds, 1);
				$hf = 1;#set high flag
			}#end if build D0-D7

		}#end if even $i 

		#TEST
		#print "i:\t$i\nD:\t$hf\n\n";
	
		#toggle clock pulse
		gpio_write($clk, $state);
		usleep($delay);#sleep .001 sec

		#lower data if high flag set
		if($hf eq 1){
			gpio_write($ds,0);
			$hf = 0;#reset high flag
		}#end if

		$i++;
	}#end while

	#latch output lines
	if($lf eq 1){	
		load();
	}#end latch flag

	gpio_write($clk,0);#set clock pulse low

}#end shift_in

sub load{

=pod
toggles LOAD pin on 7219 to send to output pins.  When the pin goes from low to high, sets output. 
=cut

	#enable XIO-p2
	gpio_enable($load, 'out');

	#ensure it is low
	gpio_write($load, 0);#low

	#go from low to high
	gpio_write($load, 1);#high
	#print "LATCH HIGH\n";
	usleep(500);#pause 0.0005 second

	#reset to low
	gpio_write($load, 0);#low
	#print "LATCH LOW\n";
	
}#end load

sub init7219{
=pod
initializes gpio lines to low
=cut
	$clk = $_[0];
	$ds = $_[1];
	$load = $_[2];

	gpio_enable($clk, 'out');
	gpio_write($clk, 0);
	gpio_enable($ds, 'out');
	gpio_write($ds, 0);
	gpio_enable($load, 'out');
	gpio_write($load, 0);
}#end init

sub print_sentence{
	#currently for a single 7219 array
	my $sentence = $_[0];
	#set to all uppercase letters
	$sentence = uc $sentence;

	#delay between words in microseconds
	my $delay = $_[1];

	#all off flag, set if reading, unset if
	#you want the text to remain on the array
	#if empty, clears the text
	my $cleartxt_flag = $_[2];

	my @words = split / /, $sentence;
	my $numwords = @words;
=pod
	foreach my $word (@words){
		print $word."\n";
	}#end foreach
=cut
	
	#print "sentence: $sentence\nNumwords: $numwords\n";
	
	#loop through words
	for(my $i=0;$i<$numwords;$i++){
		#split word into an array of chars
		my @letters = split //, $words[$i];

		#number of chars in word
		my $numalphanums = @letters;
		#print "num letters: $numalphanums\n";
			
		#first letter
		my $ln = 0;
		
		#reverse @all_digits to display words
		#left to right
		my @rev_segs = reverse @all_digits;

		foreach my $digit (@rev_segs){
		
			#limited to 8 digits for now
			shift_in($alphanums{$letters[$ln]}, $digit, 1, 250, 1 );
			#print "letter: $letters[$ln]\n";
			$ln++;#go to next letter
		}#end letters inner for loop
		
		usleep($delay);
		unless($cleartxt_flag == 1){
			all_off();
		}#end if
	}#end for numwords
}#end print_sentence

sub print_interleaved{
	#takes separate strings for each
	#line of displays. The strings are
	#assumed to fit into the 8-digit
	#line of an led array for a 7219
	my $str1 = $_[0];
	my $str2 = $_[1];

	#upper case
	$str1 = uc $str1;
	$str2 = uc $str2;

	#convert strings to array of chars
	my @s1 = split //, $str1;
	my @s2 = split //, $str2;

	#pad string arrays with spaces
	#if less than 8 chars
	my $s1_len = @s1;
	my $s2_len = @s2;
	#print "s1len: $s1_len\ts2len: $s2_len\n";

	if($s1_len < 8){
		my $nsp = 8-$s1_len;
		for(my $n=$s1_len;$n<8;$n++){
			$s1[$n] = " ";
		}#end for pad w/ spaces
	}#end pad str1 with spaces

	if($s2_len < 8){
		my $nsp = 8-$s2_len;
		for(my $n=$s2_len;$n<8;$n++){
			$s2[$n] = " ";
		}#end for pad w/ spaces
	}#end pad str1 with spaces

	$s1_len = @s1;
	$s2_len = @s2;
	#print "s1len: $s1_len\ts2len: $s2_len\n";

	#reverse @all_digits to display words
	#left to right
	my @rev_segs = reverse @all_digits;

	#init letter number to 0
	my $ln = 0;

	foreach my $digit (@rev_segs){
		shift_in($alphanums{$s2[$ln]}, $digit, 1, 250, 0 );
		shift_in($alphanums{$s1[$ln]}, $digit, 1, 250, 0 );
		load();
		$ln++;
	}#end for
}#end print_interleaved

sub turn_on{
	my $ncas = $_[0];
	if(defined($ncas)){
		#print "tu ncas defined\n";
		for(my $ni=0;$ni<$ncas;$ni++){
			shift_in(\@turn_on, \@sdreg, $ncas, 250, 0);
			if($ni ==($ncas-1)){
				#print "tu load\n";
				load();
			}#end if
		}#end for
	}#end if multiple 7219's
	else{#just one
		#set shutdown register to normal operation
		shift_in(\@turn_on, \@sdreg, 1, 250, 1);
	}#end else
}#end turn_on

sub turn_off{
	my $ncas = $_[0];
	if(defined($ncas)){
		for(my $ni=0;$ni<$ncas;$ni++){
			shift_in(\@turn_off, \@sdreg, $ncas, 250, 0);
			if($ni ==($ncas-1)){
				load();
			}#end if
		}#end for
	}#end if multiple 7219's
	else{#just one
		#set shutdown register to off
		shift_in(\@turn_off, \@sdreg, 1, 250, 1);
	}#end else
}#end turn_off

sub set_scanlimit{
	my $ncas = $_[0];
	if(defined($ncas)){
		#print "sl ncas defined\n";
		for(my $ni=0;$ni<$ncas;$ni++){
			shift_in(\@slregall, \@slimreg, $ncas, 250, 0);
			if($ni ==($ncas-1)){
				load();
				#print "sl load\n";
			}#end if
		}#end for
	}#end if multiple 7219's
	else{#just one
		#set scan limit register
		shift_in(\@slregall, \@slimreg, 1, 500, 1);
		#print "sl 1 chip\n";
	}#end else
}#end set_scanlimit

sub set_intensity{
	#takes string as arg: min, dim, mid, bright, max
	my $intensity = $_[0];
	
	#default to max
	my @intregdata = (0,0,0,0,1,1,1,1);
	if($intensity eq 'min'){
		@intregdata = (0,0,0,0,0,0,0,0);
	}#end if
	if($intensity eq 'dim'){
		@intregdata = (0,0,0,0,0,0,1,1);
	}#end if
	if($intensity eq 'mid'){
		@intregdata = (0,0,0,0,0,1,1,1);
	}#end if
	if($intensity eq 'bright'){
		@intregdata = (0,0,0,0,1,0,1,1);
	}#end if
	if($intensity eq 'max'){
		@intregdata = (0,0,0,0,1,1,1,1);
	}#end if

	#print "Intensity: $intensity\n";
	shift_in(\@intregdata, \@intreg, 1, 250, 1);
}#end set_intensity

sub all_off{
	#clear display
	#call after turned on, and
	#scan reg set to all digits
	my $ncas = $_[0];
	if(defined($ncas)){
		foreach my $digit(@all_digits){
			for(my $ni=0;$ni<$ncas;$ni++){
				shift_in(\@turn_off, $digit, $ncas, 100, 0);
				if($ni ==($ncas-1)){
					load();
				}#end if
			}#end for
		}#end outer for
	}#end if multiple 7219's
	else{#just one
		#@all_digits is an array of array references
		#representing each digit
		foreach my $digit (@all_digits){
			shift_in(\@turn_off, $digit, 1, 100, 1);
		}#end foreach
	}#end else
}#end all_off

sub disp_teston{
	#turn on display test, 
	#all digits and dp
	shift_in(\@turn_on, \@disptstreg, 1, 200, 1);
}#end disp_test

sub disp_testoff{
	#turn off display test, 
	#all digits and dp
	shift_in(\@turn_off, \@disptstreg, 1, 200, 1);
}#end disp_test

#################### EFFECTS######################
sub clockwise_circles{
	#number of iterations
	my $i = $_[0];
	my $k = 0;
	
	while($k<$i){
		for(my $n=0;$n<6;$n++){
			#outer for, each segment
			for(my $x=0; $x<8; $x++){
				#inner for, each digit
				shift_in(@all_segs[$n], @all_digits[$x], 1, 100, 1);
			}#end inner for
		}#end outer for

		$k++;

	}#end while
	all_off();
}#end clockwise_circles

sub countercw_circles{
	#number of iterations
	my $i = $_[0];
	my $k = 0;
	my @revall_segs = reverse @all_segs;
	while($k<$i){
		for(my $n=1;$n<7;$n++){
			#outer for, each segment
			for(my $x=0; $x<8; $x++){
				#inner for, each digit
				shift_in(@revall_segs[$n], @all_digits[$x], 1, 100, 1);
			}#end inner for
		}#end outer for

		$k++;

	}#end while
	all_off();
}#end countercw_circles

sub bullets_lrtop {
	#number of iterations
	my $ni = $_[0];
	for(my $n=0;$n<$ni;$n++){ for(my $i=8;$i>=0;$i--){
			shift_in(\@_seg_a, @all_digits[$i], 1, 100, 1);
			all_off();
		}#end inner for
	}#end outer for	
	all_off();
}#end bullets_lrtop

sub bullets_rltop {
	#number of iterations
	my $ni = $_[0];
	for(my $n=0;$n<$ni;$n++){
		for(my $i=0;$i<=8;$i++){
			shift_in(\@_seg_a, @all_digits[$i], 1, 100, 1);
			all_off();
		}#end inner for
	}#end outer for	
	all_off();
}#end bullets_lrtop

sub bullets_lrmid {
	#number of iterations
	my $ni = $_[0];
	for(my $n=0;$n<$ni;$n++){ for(my $i=8;$i>=0;$i--){
			shift_in(\@_seg_g, @all_digits[$i], 1, 100, 1);
			all_off();
		}#end inner for
	}#end outer for	
	all_off();
}#end bullets_lrtop

sub bullets_rlmid {
	#number of iterations
	my $ni = $_[0];
	for(my $n=0;$n<$ni;$n++){
		for(my $i=0;$i<=8;$i++){
			shift_in(\@_seg_g, @all_digits[$i], 1, 100, 1);
			all_off();
		}#end inner for
	}#end outer for	
	all_off();
}#end bullets_lrtop

sub bullets_lrbot {
	#number of iterations
	my $ni = $_[0];
	for(my $n=0;$n<$ni;$n++){ for(my $i=8;$i>=0;$i--){
			shift_in(\@_seg_d, @all_digits[$i], 1, 100, 1);
			all_off();
		}#end inner for
	}#end outer for	
	all_off();
}#end bullets_lrtop

sub bullets_rlbot {
	#number of iterations
	my $ni = $_[0];
	for(my $n=0;$n<$ni;$n++){
		for(my $i=0;$i<=8;$i++){
			shift_in(\@_seg_d, @all_digits[$i], 1, 100, 1);
			all_off();
		}#end inner for
	}#end outer for	
	all_off();
}#end bullets_lrtop
1;

Here is an example of how simple it is to use one of these LED displays using this driver.

 

#!/usr/bin/perl
use strict;
require 'MAX7219.pl';        #required driver for LED array

init7219(0,1,2);             #set the GPIO lines on the pine64 for clock, data in, and latch
turn_on(1);                  #turn on only 1 MAX7219 chip
set_scanlimit(1);            #tell it to use all 8 digits for 1 LED array
set_intensity('max');        #maximum intensity

print_sentence("pine64 rules the world", 500000);

ethical uses of nmap: scanning your home network

I routinely monitor the devices on my home network for many reasons:

  • did the kids really turn off their phones / tablets at night when I told them to?
  • has someone hacked my wifi?
  • are my routers / switches up and running?
  • are my home servers up?
  • what IP address did the device I just plugged in get from the my ISP’s router?

While there are many tools at my disposal for any of these issues, nmap is usually where I start.  While one could easily use nmap for nefarious purposes,  I focus on penetration testing networks / devices that belong to me like my home network, VPS’s that I lease, and my networking lab.  My ISP actually forbids port scanning in my terms of service, so I steer clear of doing any port scanning from home

I just plugged in my new pine64 running debian into my home network, and wanted to know the IP address so I could ssh into it.  To discover this, I did a ‘before’ scan of my network to see what was online.

nmap -T5 192.168.0.2-51

This tells nmap to scan the specified address range, and to make it quick.  Here are the results.

Starting Nmap 7.01 ( https://nmap.org ) at 2018-11-21 22:20 CST
Warning: 192.168.0.13 giving up on port because retransmission cap hit (2).
Warning: 192.168.0.8 giving up on port because retransmission cap hit (2).
Warning: 192.168.0.10 giving up on port because retransmission cap hit (2).
Warning: 192.168.0.9 giving up on port because retransmission cap hit (2).
Warning: 192.168.0.12 giving up on port because retransmission cap hit (2).
Warning: 192.168.0.2 giving up on port because retransmission cap hit (2).
Nmap scan report for 192.168.0.2
Host is up (0.0037s latency).
Not shown: 733 closed ports, 266 filtered ports
PORT STATE SERVICE
62078/tcp open iphone-sync

Nmap scan report for 192.168.0.5
Host is up (0.00031s latency).
Not shown: 999 closed ports
PORT STATE SERVICE
22/tcp open ssh

Nmap scan report for 192.168.0.7
Host is up (0.00018s latency).
Not shown: 991 closed ports
PORT STATE SERVICE
21/tcp open ftp
25/tcp open smtp
80/tcp open http
110/tcp open pop3
139/tcp open netbios-ssn
445/tcp open microsoft-ds
995/tcp open pop3s
3128/tcp open squid-http
3306/tcp open mysql

Nmap scan report for 192.168.0.8
Host is up (0.0039s latency).
Not shown: 694 closed ports, 304 filtered ports
PORT STATE SERVICE
49153/tcp open unknown
62078/tcp open iphone-sync

Nmap scan report for 192.168.0.9
Host is up (0.018s latency).
Not shown: 646 closed ports, 351 filtered ports
PORT STATE SERVICE
49152/tcp open unknown
49154/tcp open unknown
62078/tcp open iphone-sync

Nmap scan report for 192.168.0.10
Host is up (0.00027s latency).
Not shown: 790 closed ports, 203 filtered ports
PORT STATE SERVICE
21/tcp open ftp
23/tcp open telnet
80/tcp open http
515/tcp open printer
631/tcp open ipp
7443/tcp open oracleas-https
9100/tcp open jetdirect

Nmap scan report for 192.168.0.11
Host is up (0.011s latency).
Not shown: 999 closed ports
PORT STATE SERVICE
22/tcp open ssh

Nmap scan report for 192.168.0.12
Host is up (0.0020s latency).
Not shown: 671 closed ports, 328 filtered ports
PORT STATE SERVICE
62078/tcp open iphone-sync

Nmap scan report for 192.168.0.13
Host is up (0.0028s latency).
Not shown: 569 filtered ports, 430 closed ports
PORT STATE SERVICE
62078/tcp open iphone-sync

Nmap done: 50 IP addresses (9 hosts up) scanned in 32.16 seconds

Here I can see that my LAMP server is up, a few iPhones, my printer, and a few PC’s.  Now, I turn on the pine64 and rescan.

BOOYizzAH!  A new device at 192.168.0.16 with port 22 ssh open.

Nmap scan report for 192.168.0.16
Host is up (0.00032s latency).
Not shown: 999 closed ports
PORT STATE SERVICE
22/tcp open ssh

using namp for OS detection

The OS detection feature is pretty awesome.  Here, I scan  a cisco 2940 ethernet switch.


nmap -vv -T5 -A 192.168.0.200

Below is a portion of the output of this command.  It correctly guesses a cisco device (easily ascertained by the OUI section of the MAC address), specifically a 2900 series switch running IOS 12.x.

PORT STATE SERVICE REASON VERSION
23/tcp open telnet syn-ack ttl 255 Cisco router telnetd
80/tcp open http syn-ack ttl 255 Cisco IOS http config
| http-auth:
| HTTP/1.0 401 Unauthorized
|_ Basic realm=level 15 access
| http-methods:
|_ Supported Methods: GET POST
|_http-title: Authorization Required
MAC Address: 00:14:XX:XX:XX:XX (Cisco Systems)
Device type: switch
Running: Cisco IOS 12.X
OS CPE: cpe:/o:cisco:ios:12.1
OS details: Cisco 2900-series or 3700-series switch (IOS 12.1)
TCP/IP fingerprint:
OS:SCAN(V=7.01%E=4%D=11/21%OT=23%CT=1%CU=41341%PV=Y%DS=1%DC=D%G=N%M=00141C%
OS:TM=5BF63311%P=x86_64-pc-linux-gnu)SEQ(SP=106%GCD=1%ISR=10B%TI=Z%CI=I%TS=
OS:U)OPS(O1=M5B4%O2=M578%O3=M280%O4=M218%O5=M218%O6=M109)WIN(W1=1020%W2=102
OS:0%W3=1020%W4=1020%W5=1020%W6=1020)ECN(R=Y%DF=N%T=FF%W=1020%O=M5B4%CC=N%Q
OS:=)T1(R=Y%DF=N%T=FF%S=O%A=S+%F=AS%RD=0%Q=)T2(R=Y%DF=N%T=FF%W=0%S=A%A=S%F=
OS:AR%O=%RD=0%Q=)T3(R=Y%DF=N%T=FF%W=1020%S=O%A=S+%F=AS%O=M5B4%RD=0%Q=)T4(R=
OS:Y%DF=N%T=FF%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T5(R=Y%DF=N%T=FF%W=0%S=A%A=S+%F=A
OS:R%O=%RD=0%Q=)T6(R=Y%DF=N%T=FF%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T7(R=Y%DF=N%T=F
OS:F%W=0%S=A%A=S%F=AR%O=%RD=0%Q=)U1(R=Y%DF=N%T=FF%IPL=38%UN=0%RIPL=G%RID=G%
OS:RIPCK=G%RUCK=G%RUD=G)IE(R=Y%DFI=S%T=FF%CD=S)

Network Distance: 1 hop
TCP Sequence Prediction: Difficulty=262 (Good luck!)
IP ID Sequence Generation: All zeros
Service Info: OS: IOS; Devices: router, switch; CPE: cpe:/o:cisco:ios

decoding ripv2 packets with Packetor

I regularly read Daniel Miessler’s blog and his unsupervised learning newsletter.  He recently mentioned a website called Packetor which is a free online packet analyzer.

I captured some ripv2 packets on my lab network using tcpdump, and filtered for destination address 224.0.0.9, which is the multicast IP address for ripv2 routers.  Ripv2 routers blast their entire routing table to this multicast address periodically (usually every 30 seconds).  I captured the packets like so:

sudo tcpdump -i eno1 dst 224.0.0.9 -# -XX | tee ripd.txt

This command captures ripv2 packets, numbers them, displays them in hex and ascii, and pipes the output to a text file.  Here is some of the tcpdump output.

 3 21:31:38.536458 IP 192.168.0.5 > rip2-routers.mcast.net: igmp v2 report rip2-routers.mcast.net
0x0000: 0100 5e00 0009 e411 5b40 6fa8 0800 46c0 ..^.....[@o...F.
0x0010: 0020 0000 4000 0102 4361 c0a8 0005 e000 ....@...Ca......
0x0020: 0009 9404 0000 1600 09f6 e000 0009 ..............
4 21:31:44.808348 IP 192.168.0.5 > rip2-routers.mcast.net: igmp v2 report rip2-routers.mcast.net
0x0000: 0100 5e00 0009 e411 5b40 6fa8 0800 46c0 ..^.....[@o...F.
0x0010: 0020 0000 4000 0102 4361 c0a8 0005 e000 ....@...Ca......
0x0020: 0009 9404 0000 1600 09f6 e000 0009 ..............
5 21:31:55.606022 IP 192.168.0.201.route > rip2-routers.mcast.net.route: RIPv2, Response, length: 64
0x0000: 0100 5e00 0009 0014 1c61 b1b4 0800 45c0 ..^......a....E.
0x0010: 005c 0000 0000 0211 1657 c0a8 00c9 e000 .\.......W......
0x0020: 0009 0208 0208 0048 ea3d 0202 0000 0002 .......H.=......
0x0030: 0000 0a0a 0a32 ffff ffff 0000 0000 0000 .....2..........
0x0040: 0001 0002 0000 0a14 1400 ffff ff00 0000 ................
0x0050: 0000 0000 0001 0002 0000 0a32 3206 ffff ...........22...
0x0060: ffff 0000 0000 0000 0002 ..........
6 21:32:21.206294 IP 192.168.0.201.route > rip2-routers.mcast.net.route: RIPv2, Response, length: 64
0x0000: 0100 5e00 0009 0014 1c61 b1b4 0800 45c0 ..^......a....E.
0x0010: 005c 0000 0000 0211 1657 c0a8 00c9 e000 .\.......W......
0x0020: 0009 0208 0208 0048 ea3d 0202 0000 0002 .......H.=......
0x0030: 0000 0a0a 0a32 ffff ffff 0000 0000 0000 .....2..........
0x0040: 0001 0002 0000 0a14 1400 ffff ff00 0000 ................
0x0050: 0000 0000 0001 0002 0000 0a32 3206 ffff ...........22...
0x0060: ffff 0000 0000 0000 0002 ..........

This is great and all, but I wanted to see like some human-readable routing table information or something. That is where packetor came in.

I pasted the hex output of a frame from the router at 192.168.0.201 and got much more detailed information about the routing table ->

I’m impressed.   Definitely going into ctrl+d.

capturing and decoding APRS packets with software defined radio

I have a commercial FCC license (general radio operators license) as a requirement for my  current position.  I also have a general class amateur  radio license, KD5UUU, but I’ve never had any equipment.  That is, until I bought a $20 rtl-sdr radio dongle.

It’s pretty amazing what you can do with it.  Recently, I’ve been  capturing and decoding automatic packet reporting system APRS packets.  AX.25 frames are broadcast on 144.39MHz that generally are used to track vehicles by sending their gps coordinates.

There are several high-quality graphical SDR applications out there, my favorite being CubicSDR.

cubicsdr aprs monitoring
CubicSDR monitoring 144.39MHz for APRS packets using RTL-SDR on a VHF Sinclair antenna mounted at 210′

CubicSDR allows for recording live traffic.  I set it up to record a wav in mono at 44.1KHz, and to only record if a signal is breaking the squelch threshold that I set.

Here is a recording of the packets I captured.   Pretty slow day.  Recording for around 10 minutes only captured 8 discernible frames.  I received transmissions from Jackson, TN all the way to Harding University in Searcy, AR (I was located in Wynne, AR).

So what’s in there?  You need to install direwolf to see.  Once you have that installed, go to the directory where the audio is saved and use atest.


atest recording.wav

Here are the decoded frames from the recording above.

You can see that most frames contain gps info.  APRS can also be used for short text messages.  Frame #8 contains one such example ‘Wht RAM 4X4 pickup’.

You can see live and historical  APRS traffic at arps.fi on a map.  It’s pretty cool to see amateur operators travel across the screen.

An example of an amateur operator’s progress along a stretch of I-40 near Memphis, TN from https://aprs.fi

pine64 unboxing

I love tinkering with single board computers.  I was an early adopter of the CHIP  (until they tanked) and later the raspberry pi family of SBC’s.

chip single board computer
the now defunct CHIP

I was looking for a cheaper alternative to the pi model 3 ($35) yet more powerful than the pi zero w.  There were several alternatives such as the orangePi, but after reading the forums, it looked like there were some serious overheating problems and poor support from the community.  I came across the pine64 and just had to get one.

I haven’t done much with it yet; just burned debian + mate desktop that a member of the community made available, but so far I am impressed.  I got the base Allwinner quad-core 64-bit ,512MB  memory model for just $15!

pine64 processor info

Somewhat larger than the pi 3, it has 10/100 ethernet, HDMI, 2x USB ports, and lots of GPIO pins.   I can’t wait to get started.

pine64 vs raspberry pi 3b
pine64 vs raspberry pi 3b

 

perl regex script for IP traffic analysis

Perl is my language of choice for just about everything: shell scripting, windows applications using Tk, Iot, raspberry pi GPIO, and sometimes even web via CGI (an anachronism, I know).  It’s just so easy to do something really powerful with little effort.

I have been using Wireshark since it was ethereal, back in the early 2000’s when I was caught up deep into the Cisco certification racket.  Lately, I prefer the granular control of tcpdump from the command line.  One of Perl’s many great strengths is of course it’s regular expression capabilities.  One night, just for fun, I wanted to analyze my TCP/IP traffic as I was casually browsing some of my usual sites.  I ran the following command and had the output piped to a text file:


sudo tcpdump -i eth2 -n -# dst 192.168.0.3 | tee dump.txt

Pretty simple.  It tells tcpdump to monitor my ethernet interface, not to resolve hostnames, number each packet, record only packets destined for my PC, and write the results in dump.txt.   You immediately see results on the screen:

tcpdump perl
tcpdump results

I wrote a very simple script off the top of my head to scan the results file, dump.txt, for IPv4 addresses, and then tell me the number of packets received from that address, the name of the organization that it originated from, and the country of origin.

The Perl regex that I came up with for finding an IP address in a text file is embarrassing simple, but very effective.

while($_ =~ /(\d+\.\d+\.\d+\.\d+)/g){
...
}#end while

Does it test for invalid addresses? No.  But after using this for some time,  it has never failed to find every IP address in the output of a tcpdump capture.   My script looks at each IP address and runs a whois command for each unique address.  I then use more regexes to find the organization and countries of origin.  below is a sample output.

This particular capture was very brief, and only contained 7 unique addresses.  This script, however, can work for hundreds or thousands of IP addresses.

I also used this script to parse my auth.log file on one of my internet-facing home servers.  I stupidly had my home server with port 22 open for ssh.  I was constantly  being hit with attempts to login to my server with well known usernames and passwords literally all day long.  (I changed the ssh port later and nearly all of this stopped!)  Most of these login attempts were from foreign countries, no doubt running a script.  I was first alerted to this problem by running

netstat -t

and seeing a lot of tcp connections to strange addresses that I was sure were unsolicited, and that I did not initiate.
Here is the script in it’s entirety.

#!/usr/bin/perl -w
use strict;
$| = 1;

=pod
parses tcpdump file for ip addresses

example for creating file:
sudo tcpdump -i eth0 > dump.txt

=cut

open FH, "dump.txt" or die $!;

#array for IP addresses
my @ips;
my @uips;            #unique IP address array
my $ipex_f = 0; #flag to test t/f ip exists in array

my $ln = 1;
while(){

while($_ =~ /(\d+\.\d+\.\d+\.\d+)/g){
push @ips, $1;
#print "$1\ton line $ln\n";

#reset each line
$ipex_f = 0;
my $dexist = 0;

#see if IP in array, if not push on unique IP array
foreach my $ip (@ips){
if($ip eq $1){
$dexist++;
}#end if
}#end foreach

if($dexist == 1){
push @uips, $1;
}#end if
}#end if

$ln++;
}#end while

my $n_ip = @uips;
print "$n_ip IP addresses found......\n-----------------------------------------\n";
foreach my $addr (@uips){
print $addr."\n";
}#end foreach

my $n=1;
foreach my $ipa (@uips){
print "\n$n--------------------------------------------\n";
my $n_occur = 0;

foreach my $n (@ips){
if($n eq $ipa){
$n_occur++;
}#end if
}#foreach

print "$ipa\t$n_occur \n";
my $whois = `whois $ipa > whois.txt`;
open WT, "whois.txt" or die $!;
while(){
if($_ =~ /Organization/i){ print $_; }
if($_ =~ /Country/i){ print $_; }
}#end while
close WT;

$n++;
}#end foreach

close FH;

Simple, but effective.  You could use this on ANY text document that contains IP addresses.

cisco 2801 & 2621XM load testing with iperf

I just wanted to see what would happen to the CPU of a few Cisco routers if I flooded them with traffic using iperf.  Specifically, I wondered if it was possible to ‘weaponize’ iperf by setting up multiple clients sending tons of traffic across a network to an iperf sink.

I set up an iperf server on a 10.3 FreeBSD machine like so:


iperf -s

freeBSD iperf server

As you can see, the client at 192.168.0.3 sent 444MB in 60 seconds.  What was really interesting to me was the CPU load on the Cisco 2621XM router that is connected to the 10.30.30.0 network……… nearly 90%!!!

From what I understand, the Cisco 2621XM router has an MPC860 processor capable of 88MIPS at 66MHz.  I would posit that is is possible to disable a router such as this with an iperf attack coming from multiple devices.  I mean, only one iperf client nearly maxed it out.

Now for the more powerful Cisco 2801.  CPU utilization hovers near 50%.

Even with multiple clients sending traffic to the iperf server, the CPU utilization never increases.  I assume this is due to the CEF (cisco express forwarding) functionality of the router, but I am not for sure.

replacing static routes with quagga rip v2 on ubuntu

I have been experimenting with the network traffic generation tool iperf in my home networking lab to load test a few Cisco routers.  I was curious what the CPU load would be on the routers with multiple PC’s sending traffic across several routed subnets.  I configured two PC’s on my workstation segment, 192.168.0.0/24,  as iperf clients and set up a dual-core Pentium 4 FreeBSD 10.3 machine as the iperf server that spanned  a Cisco 2801 and a Cisco 2621XM like so:

iperf load testing
iperf lab load test setup

The routers use RIP v2 routing protocol to learn all of the configured routes they have consisting of several VLSM 10.X.X.X and 192.168.X.X networks.   I was initially using static routing on my iperf client PC’s to send packets to the iperf sink PC on the 10.30.30.0 network like so:


sudo route add -net 10.0.0.0/8 gw 192.168.0.201

This got somewhat tedious after time, having to run this on multiple machines before I could do any testing.  A few years ago, I had done some experimentation with quagga.  Quagga is a software defined networking utility that can change an x-nix PC into a router that can learn IP routes using protocols like RIPv2, OSPF,  BGP, et.al.  I used RIPv2 for simplicity.

FYI: this is not a tutorial on installing or configuring quagga or iperf.  There are many tutorials out there for that.

After configuring quagga and the ripd daemon on my iperf client PC’s,  they automatically learned the routes to all the 10 and 192.168 networks in my lab with no manual configuration on my part.

To see the routes my PC learned via ripd + quagga, run the following command:


telnet localhost zebra
show ip route

Here, you can see the routes the PC learned via RIPv2 from the Cisco routers.

The following tcpdump command can view the RIPv2 packets coming from the PC’s neighboring router:


sudo tcpdump -i eth2 udp port 520 -XX -#

Now, each time I turn on my PC’s, they learn the routes in my lab automatically with no configuration on my part.

Next time, I will post the results of the CPU load on the Cisco routers using iperf testing.