Technical Notebook

One of my online technical notebooks. This one made using BookStack.

Asterisk

Content migrated from here.

Asterisk Download Links

http://downloads.asterisk.org/pub/telephony/

http://downloads.asterisk.org/pub/telephony/dahdi-linux/

http://downloads.asterisk.org/pub/telephony/dahdi-tools/

http://downloads.asterisk.org/pub/telephony/libpri/

http://downloads.asterisk.org/pub/telephony/asterisk/

http://svnview.digium.com/svn

http://svnview.digium.com/svn/asterisk/

Hacks and Patches

Converting audio files for use in Asterisk

Resample a stereo wav into a mono 8000Hz 16 bit wav:
sox infile.wav -c mono -r 8000 -b 16 outfile.wav

Convert a properly formatted wav (8000Hz, 16bit mono) file to gsm:
sox infile.wav outfile.gsm

Converting mp3 to slin

mpg123 -w file.wav file.mp3
sox file.wav -t raw -r 8000 -s -2 -c 1 file.sln

Converting wav to slin

sox file.wav -t raw -r 8000 -s -2 -c 1 file.sln

Upgrading asterisk 1.4 to 1.6

NoOp updates

/NoOp

:%s/\(NoOp,\)\(.*\)$/NoOp(\2)/
sed -e 's/\(NoOp,\)\(.*\)$/NoOp(\2)/'

pipe character to comma

:%s/|/,/g

ExecIf updates

/ExecIf

:%s/\(ExecIf(\)\(\$\[.*\]\),\(.*\),\(.*\)$/\1\2?\3(\4)/
sed -e 's/\(ExecIf(\)\(\$\[.*\]\),\(.*\),\(.*\)$/\1\2?\3(\4)/'

Deprecated stuff - dialplan
grep -ir \
-e SetCallerPres \
-e WaitMusicOnHold \
-e SetMusicOnHold \
-e QUEUE_MEMBER_COUNT \
-e Local \
*

Faxing

Changes that can be made on the Brother MFC-8860DN to better optimize for using with VOIP.

Resolution press MENU/SET, 2, 2, 2, choose standard. Press MENU/SET, STOP,EXIT.

Baud (baud rate)/ECM press MENU/SET, 2, 0, 1, which is compatiability. Press up or down arrow to choose basic. Press MENU/SET, STOP/EXIT. This sets baud to 9600 and disables ECM.

Building

cd /usr/src/asterisk/mpg123-0.59r-gpl
make linux-x86_64 ; make install ; cd ..

cd /usr/src/asterisk/1.6.2.4

cd spandsp-0.0.6
./configure ; make install ; ldconfig ; cd ..

cd corosync-1.2.0
./configure ; make install ; cd ..

cd openais-1.1.2
./configure ; make install ; cd ..

cd dahdi-linux-2.2.1
make install ; cd ..

cd dahdi-tools-2.2.1
./configure ; make menuconfig ; make install ; make config ; make samples ; cd ..

cd libpri-1.4.10.2
make install ; cd ..

cd asterisk-1.6.2.4
./configure ; make menuconfig ; make install ; make samples ; make config ; cd ..

cd asterisk-addons-1.6.2.0
./configure ; make menuconfig ; make install ; make samples ; cd ..

cd ..

Simplify Asterisk 1.6 Dialplan Extensions

:%s/exten => .*,n/same => n/g

Sipura Distinctive Ring

exten => s,n,SIPAddHeader(Alert-Info: info=<Bellcore-r2>)

Available Distinctive Ring Patterns:

Pattern Name   Distinctive Ring Patterns
============   =========================
Bellcore-r1    60(2/4)
Bellcore-r2    60(.8/.4,.8/4)
Bellcore-r3    60(.4/.2,.4/.2,.8/4)
Bellcore-r4    60(.3/.2,1/.2,.3/4)
Bellcore-r5    1(.5/.5)
Bellcore-r6    60(.2/.4,.2/.4,.2/4)
Bellcore-r7    60(.4/.2,.4/.2,.4/4)
Bellcore-r8    60(0.25/9.75)

Available Distinctive Call Waiting Tone Patterns:

Pattern Name   Tone Pattern
============   ============
Bellcore-r1    30(.3/9.7)
Bellcore-r2    30(.1/.1, .1/9.7)
Bellcore-r3    30(.1/.1, .1/.1, .1/9.7)
Bellcore-r4    30(.1/.1,.3/.1,.1/9.3)
Bellcore-r5    1(.5/.5)
Bellcore-r6    30(.1/.1,.3/.2,.3/9.1)
Bellcore-r7    30(.3/.1,.3/.1,.1/9.1)
Bellcore-r8    2.3(.3/2)

Using AWK to find individual calls

The below scripting will find all calls to the extension 11175 and print all the details for each call.

grep Dial /var/log/asterisk/full | grep macro-stdexten-v3 | grep "11175," | awk -F\[ '{print $3}' | awk -F\] '{print $1}' | while read x; do echo ""; echo ""; echo ""; echo "identifier: $x"; echo ""; echo ""; echo ""; grep "\[$x\]" /var/log/asterisk/full; done



grep "Called .*11175" /var/log/asterisk/full | awk -F\[ '{print $3}' | awk -F\] '{print $1}' | while read x; do echo ""; echo ""; echo ""; echo "identifier: $x"; echo ""; echo ""; echo ""; grep "\[$x\]" /var/log/asterisk/full; done



grep "Called .*11175" /var/log/asterisk/full | awk -F\[ '{print $3}' | awk -F\] '{print $1}' | while read x; do echo ""; echo ""; echo ""; echo "identifier: $x"; echo ""; echo ""; echo ""; grep "\[$x\]" /var/log/asterisk/full; done | grep -A 5 -e "Called .*11175"

Finding hung channels

Asterisk 1.4,1.6
asterisk -rx "core show channels concise" | awk -F ! '{print $1,$11}'

asterisk -rx "core show channels concise" | awk -F ! '{print $1,$11}' | while read x y; do if [ $y -gt 60 ] || [ $y -lt 0 ]; then echo "$x - `expr $y / 60` minutes `expr $y % 60` seconds"; fi ; done

Asterisk 1.2
asterisk -rx "show channels concise" | awk -F : '{print $1,$11}'

Asterisk and SystemD

See here...

Put the following in /etc/systemd/system/asterisk.service and then run "systemctl daemon-reload && systemctl enable asterisk"

[Unit]
Description=Asterisk PBX And Telephony Daemon
Wants=network.target
After=network.target

[Service]
Type=simple
User=root
Group=root
#Environment=HOME=/var/lib/asterisk
#WorkingDirectory=/var/lib/asterisk
ExecStart=/usr/sbin/asterisk -f -C /etc/asterisk/asterisk.conf
ExecStop=/usr/sbin/asterisk -rx 'core stop now'
ExecReload=/usr/sbin/asterisk -rx 'core reload'

LimitNOFILE=65535

# safe_asterisk emulation
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

Paste below into a terminal to setup the files:

cat << EOF > /etc/systemd/system/asterisk.service
[Unit]
Description=Asterisk PBX And Telephony Daemon
Wants=network.target
After=network.target

[Service]
Type=simple
User=root
Group=root
#Environment=HOME=/var/lib/asterisk
#WorkingDirectory=/var/lib/asterisk
ExecStart=/usr/sbin/asterisk -f -C /etc/asterisk/asterisk.conf
ExecStop=/usr/sbin/asterisk -rx 'core stop now'
ExecReload=/usr/sbin/asterisk -rx 'core reload'

LimitNOFILE=65535

# safe_asterisk emulation
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

EOF

Loading Dahdi

After some digging, for my purposes I found the best way to load the transcoding module is to use the systemd module loader to load the wctc4xxp transcoding module and UDEV to run dahdi_cfg once the module is loaded.

Prerequisite: Configure the /etc/dahdi/modules and /etc/dahdi/system.conf files as you normally would.

cat << EOF > /etc/dahdi/modules
# /etc/modules-load.d/dahdi is symlinked here so systemd will load it on startup
# /etc/udev/rules.d/dahdi-wctc4xxp.rules instructs udev to run dahdi_cfg after wctc4xxp module is loaded

# Digium TC400B: G729 / G723 Transcoding Engine
wctc4xxp

EOF

ln -s /etc/dahdi/modules /etc/modules-load.d/dahdi.conf

# update udev to run dahdi_cfg after loading the transcoding module
cat << EOF > /etc/udev/rules.d/dahdi-wctc4xxp.rules
KERNEL=="wctc4xxp" RUN+="/usr/sbin/dahdi_cfg"
EOF

The next time you reboot, systemd will load the module, udev will run dahdi_cfg, and then systemd will load asterisk. Granted this is only really needed if you haven't migrated away from MeetMe yet...

GROUP and GROUP_COUNT

func_GROUP

func_GROUP_COUNT

Set(GROUP(category)=groupname)
Set(CHECKING=${GROUP_COUNT(groupname@category)})

Example:

same => n,Set(GROUP(rcf)=${CHANNEL(accountcode)})
same => n,Set(GROUP(rcf${RCF_DID})=${CHANNEL(accountcode)})

same => n,Set(rcfCountAccountcode=${GROUP_COUNT(${CHANNEL(accountcode)}@rcf)})
same => n,Set(rcfCountDID=${GROUP_COUNT(${CHANNEL(accountcode)}@rcf${RCF_DID})})

Asterisk comparisons

same => n,ExecIf($["${testVariable}" = "SomeValue"]?Set(testvar=TRUE):Set(testvar=FALSE))

; Numberical comparisons require not using quotes
same => n,Set(count=5)
same => n,GofoIf($[${count} < 10]?trueTarget:falseTarget)



; Check if a variable is NOT NULL - This one will set testVariable2=TRUE
same => n,Set(testVariable1=something)
same => n,ExecIf($[!${LEN(${testVariable1})}]?Set(testVariable2=TRUE):Set(testVariable2=FALSE))

; Check if a variable is NOT NULL - This one will set testVariable2=FALSE
same => n,Set(testVariable1=)
same => n,ExecIf($[!${LEN(${testVariable1})}]?Set(testVariable2=TRUE):Set(testVariable2=FALSE))



; Multiple comparisons using AND (&)
same => n,ExecIf($["${testVariable1}" = "VALID" & "${testVariable2}" = "VALID"]?set(status=TRUE))

; Multiple comparisons using OR (|)
same => n,ExecIf($["${testVariable1}" = "VALID" | "${testVariable2}" = "VALID"]?set(status=TRUE))



; Providing only a false conditional path
same => n,Set(testVariable1=1)
same => n,GotoIf(${}?:falseCondition)
same => n(trueCondition),NoOp(do something on true condition)
same => n,Hangup()
same => n(falseCondition),NoOp(do something on false condition)
same => n,Hangup()

[end]

Batteries

Lithium Ion Voltage vs. Charge Status

Original Source

4.2V – 100%
4.1V – 87%
4.0V – 75%
3.9V – 55%
3.8V – 30%
3.5V – 0%

Somewhere in the 3.9V or slightly below area would be ideal for storage. Just don't overdo it. I believe AW batteries generally ship about 40% charge, or you can just discharge them in a light or other device, since that is easy and safe.

 

Databases

Databases

MySQL

Grants

GRANT ALL ON *.* TO 'user'@'localhost' IDENTIFIED BY 'somepassword';
GRANT ALL ON database.* TO 'user'@'localhost' IDENTIFIED BY 'somepassword';
GRANT ALL ON database.table TO 'user'@'localhost' IDENTIFIED BY 'somepassword';

GRANT insert ON *.* TO 'user'@'localhost' IDENTIFIED BY 'somepassword';
GRANT select ON database.* TO 'user'@'localhost' IDENTIFIED BY 'somepassword';
GRANT insert,select,update ON database.table TO 'user'@'localhost' IDENTIFIED BY 'somepassword';

Reset lost root password

/etc/init.d/mysqld stop

mysqld_safe --skip-grant-tables --skip-networking &
sleep 5
mysql -u root

use mysql;
update user set password=PASSWORD("toor") where User='root';
flush privileges;
quit

/etc/init.d/mysqld restart

Linux

Linux related stuff

Linux

Bandwidth monitoring

Useful console programs

To install them all:

sudo apt install bwm-ng iftop nethogs nload

Useful gui programs

Linux

VBAN for Linux

https://github.com/quiniouben/vban

I've used Voicemeeter Banana and Potato for a long time to do advanced audio management on my various computers, including streaming audio from various computers in the house to my laptop and vice versa.

VBAN for Linux allows me to incorporate some older laptops (that have trouble running Windows but no problem running Ubuntu Desktop) into my various setups. Primarily I use a second laptop to stream Youtube or Udemy videos while I'm using my primary Windows laptop. Bluetooth headset is connected to the primary laptop and all other audio sources are sinked to it. 👍

Configuring Ubuntu 20.04 to sink audio and use vban_emitter to stream to a VBAN receiver

Preparing Pulseaudio
#!/bin/bash

pactl load-module module-null-sink sink_name=vbanmix

# use "pactl info" or "pactl list" to find the proper alsa_output interface
pactl load-module module-combine-sink channels=2 slaves=vbanmix,alsa_output.pci-0000_00_1b.0.analog-stereo

pactl set-default-source vbanmix.monitor
Running vban_emitter
#!/bin/bash

IPADDR=10.10.10.10
UDPPORT=6980
STREAMNAME=Linux_Laptop
SAMPLERATE=48000

AUDIOBACKEND=pulseaudio

vban_emitter --ipaddress=$IPADDR --port=$UDPPORT --streamname=$STREAMNAME --backend=$AUDIOBACKEND --rate=$SAMPLERATE
Linux

Useful system commands

Memory

# Show maximum capacity and number of RAM slots
sudo dmidecode -t 16
# Show information on RAM in slots
sudo dmidecode -t 17

# Shows all memory related information in the system. Slow as it polls all hardware first.
sudo lshw -class memory

Microsoft

Microsoft

Excel

Conditional highlighting with functions

This is a collection of frequently used functions for conditional highlighting in Excel.

Highlight cells that are formatted dates and the dates are older than X days. Applies to =$A:$A if all dates you want highlighted are in the A column.

=IF(LEFT(CELL("format",A1),1)="D",IF(A1<TODAY()-37,TRUE,FALSE),FALSE)

:end

Microsoft

Installing Certificates on Windows

Using PowerShell to install into the Local Computer store

This can only be done with elevated privileges.

Import-Certificate -CertStoreLocation Cert:\LocalMachine\Root -FilePath cert.crt
Microsoft

Microsoft Remote Desktop Certificates

Manually replacing RDP certificate

Install the new certificate in the Local Computer Personal store:

If no password is needed:

Import-PfxCertificate -CertStoreLocation Cert:\LocalMachine\My -FilePath cert.pfx

Or if a password is needed:

$mypwd = Get-Credential -UserName 'Enter password below' -Message 'Enter password below'
Import-PfxCertificate -CertStoreLocation Cert:\LocalMachine\My -Password $mypwd.Password -FilePath cert.pfx

After installing the new certificate in the Local Computer Personal store, run the following commands:

Set-Location Cert:\LocalMachine\my
Get-ChildItem

Pick the Thumbprint of the certificate you wish to use then run the following command using the proper thumbprint:

#Replace Certificate for RDS
wmic /namespace:\\root\cimv2\TerminalServices PATH Win32_TSGeneralSetting Set SSLCertificateSHA1Hash="[new_cert_thumbprint]"

This effectively updates the registry key:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp\SSLCertificateSHA1Hash

Use the following command to verify that the proper certificate is being used:

Get-WmiObject "Win32_TSGeneralSetting" -Namespace root\cimv2\terminalservices -Filter "TerminalName='RDP-tcp'"

Source

Windows Server 2022 method

Couldn't the WMI method to work on Windows Server 2022. This did.

$serverName = "MYTS01"
$certHash = "xxxxx"
$path = (Get-WmiObject "Win32_TSGeneralSetting" -ComputerName $serverName -Namespace root\cimv2\terminalservices -Filter "TerminalName='RDP-tcp'")
Set-WmiInstance -Path $path -Arguments @{SSLCertificateSHA1Hash=$certHash}

Signing RDP files

Use the rdpsign app to

rdpsign /sha256 xxxxxxxx "Remote Desktop File.rdp"

 

Microsoft

Network Policy Server / NPS

 

Enable NTLMv2 support for MSCHAPv2 RADIUS requests

Enables proxied radius requests when using things like radsecproxy, duoauthproxy, etc. Avoids the MS-CHAP-Error responses.

$registryPath = "HKLM:\System\CurrentControlSet\Services\RemoteAccess\Policy"
$propertyName = "Enable NTLMv2 Compatibility"
$propertyValue = "1"

New-ItemProperty -Path $registryPath -Name $propertyName -Value $propertyValue -PropertyType DWORD -Force

Stop-Service IAS
Sleep 2
Start-Service IAS

Source 1 / Source 2

Microsoft

Other Useful PowerShell Commands

Placeholder

Format processes by start date

This command will show a lot of errors if you're not running PowerShell as Administrator.

Get-Process | Sort-Object StartTime | Format-Table -View StartTime

Placeholder

Sources:
PowerShell Format-Table

Microsoft

Querying Event Logs

I noticed that there is a huge speed difference between using an XML Query and PowerShell Get-EventLog piped through Where-Object to filter event logs. Thanks to this article, I learned how to use the XML Query via PowerShell, so you get the best of both worlds.

Finding account lockouts.

XML Query

Use this query in the Windows Event Viewer

<QueryList>
  <Query Id="0" Path="Security">
    <Select Path="Security">
      *[
        System[(EventID='4740')]
      ] 
    </Select>
  </Query>
</QueryList>

PowerShell Script - Slow method

Get-EventLog -LogName Security | Where-Object {$_.EventID -eq 4740} |
   Select-Object -Property TimeGenerated, Source, EventID, InstanceId, Message

PowerShell Script - Fast method

$query = @"
<QueryList>
  <Query Id="0" Path="Security">
    <Select Path="Security">
      *[
        System[(EventID='4740')]
      ] 
    </Select>
  </Query>
</QueryList>
"@

Get-WinEvent -FilterXml $query | Format-List

Finding account lockouts for a particular user.

XML Query

<QueryList>
  <Query Id="0" Path="Security">
    <Select Path="Security">
      *[
        EventData[Data[@Name='TargetUserName']='administrator']
        and
        System[(EventID='4740')]
      ] 
    </Select>
  </Query>
</QueryList>

PowerShell Script - Fast method

$query = @"
<QueryList>
  <Query Id="0" Path="Security">
    <Select Path="Security">
      *[
        EventData[Data[@Name='TargetUserName']='administrator']
        and
        System[(EventID='4740')]
      ] 
    </Select>
  </Query>
</QueryList>
"@

Get-WinEvent -FilterXml $query | Format-List

NPS + Azure MFA Logs

XML Query

<QueryList>
  <Query Id="0" Path="System">
    <Select Path="System">*[System[Provider[@Name='NPS']]]</Select>
    <Select Path="Security">*[System[Provider[@Name='Microsoft-Windows-Security-Auditing'] and Task = 12552]]</Select>
    <Select Path="AuthNOptCh">*</Select>
    <Select Path="AuthZAdminCh">*</Select>
    <Select Path="AuthZOptCh">*</Select>
  </Query>
</QueryList>

 

Sources: 1

Networking

Networking

MAC Addresses

MAC OUI

 

Reserved OUIs

Addresses Usage Reference
00-00-00 to 00-00-FF Reserved [RFC7042]
00-01-00 to 00-01-FF VRRP (Virtual Router Redundancy Protocol) [RFC5798]
00-02-00 to 00-02-FF VRRP IPv6 (Virtual Router Redundancy Protocol IPv6) [RFC5798]
00-03-00 to 00-51-FF Unassigned  
00-52-00 PacketPWEthA [RFC6658]
00-52-01 PacketPWEthB [RFC6658]
00-52-02 BFD for VXLAN [RFC8971]
00-52-03 to 00-52-12 Unassigned (small allocations)  
00-52-13 Proxy Mobile IPv6 [RFC6543]
00-52-14 to 00-52-FF Unassigned (small allocations)  
00-53-00 to 00-53-FF Documentation [RFC7042]
00-54-00 to 90-00-FF Unassigned  
90-01-00 TRILL OAM [RFC7455]
90-01-01 to 90-01-FF Unassigned (small allocations requiring both unicast and multicast)  
90-02-00 to FF-FF-FF Unassigned  

Source

OUIs of virtualization platforms

Company and Products MAC unique identifier (s)
VMware ESX 3, Server, Workstation, Player 00-50-56, 00-0C-29, 00-05-69
Microsoft Hyper-V, Virtual Server, Virtual PC

00-03-FF

00-15-5D

Parallells Desktop, Workstation, Server, Virtuozzo 00-1C-42
Virtual Iron 4 00-0F-4B
Red Hat Xen 00-16-3E
Oracle VM 00-16-3E
XenSource 00-16-3E
Novell Xen 00-16-3E
Sun xVM VirtualBox 08-00-27

Source

End

Networking

Microsoft Network Monitor

I had never heard of this tool until today... I've always used Wireshark. Today I needed to view traffic broken out by application (PID/ProcessName). I went hunting and found the Microsoft Network Monitor. Surprisingly it's very feature rich, easy to use, and did exactly what I needed it to do... and sooo much more. Check it out!

Example Filters

Capturing everything except RDP:

!(tcp.port==3389)

Capture only DNS:

DNS

Filter Source or Destination IPv4 Address:

IPv4.Address == 1.1.1.1

Filter Source IPv4 Address:

IPv4.SourceAddress == 1.1.1.1

Filter IPV4 Source and Destination:

IPv4.Address==1.1.1.1 and IPv4.Address==2.2.2.2

Filter IPv4 Source or Destination to subnet:

((ipv4.Address & 255.0.0.0) == 10.0.0.0)

Filter IPv4 traffic to private only traffic (source and destination in RFC-1918 private subnets):

(((IPv4.SourceAddress & 255.0.0.0) == 10.0.0.0) || ((IPv4.SourceAddress & 255.240.0.0) == 172.16.0.0) || ((IPv4.SourceAddress & 255.255.0.0) == 192.168.0.0))
&&
(((IPv4.DestinationAddress & 255.0.0.0) == 10.0.0.0) || ((IPv4.DestinationAddress & 255.240.0.0) == 172.16.0.0) || ((IPv4.DestinationAddress & 255.255.0.0) == 192.168.0.0))

Filter traffic by ProcessName

The filter below allows you to see if a process is communicating with any other IP address besides the one you listed:

ProcessName.Contains("WindTerm.exe") && IPv4.Address!= 9.9.9.9

 

Example on other sites:


-end

Networking

Mikrotik

Stream packet sniffer to Wireshark

Setup system emailer

I have noticed that in RouterOS v2, the emailer uses the system identity as the HELO/EHLO host name. Some mail servers won't accept a host name with spaces or other characters. RouterOS v3 doesn't seem to be effected by this.

The following is for version 3 and 4:

/tool e-mail 
set server=69.18.98.42:587 from="mikrotik@change.me" 
/system watchdog
set auto-send-supout=yes send-email-to=notify@change.me send-smtp-server=69.18.98.42

The following is required for version 5:

/tool e-mail 
set address=69.18.98.42 port=587 from="mikrotik@change.me" 
/system watchdog
set auto-send-supout=yes send-email-to=notify@change.me send-smtp-server=69.18.98.42

NTP Client and Time Zone

/system clock
set time-zone-name=America/Chicago
/system ntp client
set enabled=yes mode=unicast primary-ntp=129.6.15.28 secondary-ntp=129.6.15.29

Google Public DNS Settings

For newer routers:

/ip dns
set allow-remote-requests=yes cache-max-ttl=1w cache-size=2048KiB max-udp-packet-size=512 servers=8.8.8.8,8.8.4.4

For older routers:

/ip dns
set allow-remote-requests=yes cache-max-ttl=1w primary-dns=8.8.8.8 secondary-dns=8.8.4.4

OpenDNS DNS Settings

For newer routers:

/ip dns
set allow-remote-requests=yes cache-max-ttl=1w cache-size=2048KiB max-udp-packet-size=512 servers=208.67.222.222,208.67.220.220

For older routers:

/ip dns
set allow-remote-requests=yes cache-max-ttl=1w primary-dns=208.67.222.222 secondary-dns=208.67.220.220

Protecting your WAN Interface

# Pick your WAN input interface
# DO NOT COPY AND PASTE ALL THESE RULES!!!

/ ip firewall filter

add chain=input in-interface=ether1 action=jump jump-target=protect-wan-input src-address-list=!allowed-management comment="Filter WAN input" disabled=no 
add chain=input in-interface=ether2 action=jump jump-target=protect-wan-input src-address-list=!allowed-management comment="Filter WAN input" disabled=no 
add chain=input in-interface=ether3 action=jump jump-target=protect-wan-input src-address-list=!allowed-management comment="Filter WAN input" disabled=no 
add chain=input in-interface=ether4 action=jump jump-target=protect-wan-input src-address-list=!allowed-management comment="Filter WAN input" disabled=no 
add chain=input in-interface=ether5 action=jump jump-target=protect-wan-input src-address-list=!allowed-management comment="Filter WAN input" disabled=no 
add chain=input in-interface=ether6 action=jump jump-target=protect-wan-input src-address-list=!allowed-management comment="Filter WAN input" disabled=no 
add chain=input in-interface=ether7 action=jump jump-target=protect-wan-input src-address-list=!allowed-management comment="Filter WAN input" disabled=no 
add chain=input in-interface=ether8 action=jump jump-target=protect-wan-input src-address-list=!allowed-management comment="Filter WAN input" disabled=no 
add chain=input in-interface=ether9 action=jump jump-target=protect-wan-input src-address-list=!allowed-management comment="Filter WAN input" disabled=no 
add chain=input in-interface=ether10 action=jump jump-target=protect-wan-input src-address-list=!allowed-management comment="Filter WAN input" disabled=no 
add chain=input in-interface=ether11 action=jump jump-target=protect-wan-input src-address-list=!allowed-management comment="Filter WAN input" disabled=no 

add chain=input in-interface=pppoe action=jump jump-target=protect-wan-input src-address-list=!allowed-management comment="Filter WAN input" disabled=no 
 
add chain=input action=jump jump-target=protect-wan-input in-interface=vlan src-address-list=!allowed-management comment="" disabled=no 

# The protect-wan-input chain

/ ip firewall filter  
add action=drop chain=protect-wan-input comment="Protect services running on the WAN interface" disabled=no dst-port=21 protocol=tcp 
add action=drop chain=protect-wan-input comment="" disabled=no dst-port=22 protocol=tcp 
add action=drop chain=protect-wan-input comment="" disabled=no dst-port=23 protocol=tcp 
add action=drop chain=protect-wan-input comment="" disabled=no dst-port=53 protocol=tcp 
add action=drop chain=protect-wan-input comment="" disabled=no dst-port=53 protocol=udp 
add action=drop chain=protect-wan-input comment="" disabled=no dst-port=80 protocol=tcp
add action=drop chain=protect-wan-input comment="" disabled=no dst-port=443 protocol=tcp 
add action=drop chain=protect-wan-input comment="" disabled=no dst-port=3128 protocol=tcp 
add action=drop chain=protect-wan-input comment="" disabled=no dst-port=8080 protocol=tcp 
add action=drop chain=protect-wan-input comment="" disabled=no dst-port=64872-64875 protocol=tcp 
add action=jump chain=protect-wan-input comment="" disabled=no jump-target=manage-icmp protocol=icmp 
add chain=protect-wan-input action=accept comment="Log traffic WAN traffic" disabled=no

# manage-icmp chain

/ ip firewall filter 
add chain=manage-icmp protocol=icmp icmp-options=8:0 action=accept comment="Allow pings" disabled=no 
add chain=manage-icmp protocol=icmp icmp-options=0:0 action=accept comment="Accept responses to our pings" disabled=no 
add chain=manage-icmp protocol=icmp icmp-options=3:0 action=accept comment="# Accept notifications of unreachable hosts" disabled=no 
add chain=manage-icmp protocol=icmp icmp-options=4:0 action=accept comment="# Accept notifications to reduce sending speed" disabled=no 
add chain=manage-icmp protocol=icmp icmp-options=11:0 action=accept comment="# Accept notifications of lost packets" disabled=no 
add chain=manage-icmp protocol=icmp icmp-options=12:0 action=accept comment="# Accept notifications of protocol problems" disabled=no 
add chain=manage-icmp protocol=icmp action=drop comment="Drop all other ICMP traffic" disabled=no

Traffic Marking and outbound queueing

/ip firewall mangle
add action=jump chain=prerouting comment="Jump to mark-traffic: MUST RETURN FROM THIS JUMP" disabled=no jump-target=mark-traffic
add action=jump chain=output comment="Jump to mark-traffic: MUST RETURN FROM THIS JUMP" disabled=no jump-target=mark-traffic packet-mark=no-mark
add action=mark-packet chain=mark-traffic comment="mark-traffic: default mark bulk" disabled=no new-packet-mark=bulk passthrough=yes
add action=mark-packet chain=mark-traffic comment="mark-traffic: voice" disabled=no dst-address-list=voice-servers new-packet-mark=voice passthrough=yes
add action=return chain=mark-traffic comment="" disabled=no packet-mark=voice
add action=return chain=mark-traffic comment="mark-traffic: end of chain return" disabled=no

/queue type
add kind=sfq name=qos sfq-allot=1514 sfq-perturb=5

/queue tree
add burst-limit=0 burst-threshold=0 burst-time=0s disabled=no limit-at=0 max-limit=0 name=queue-wan1-root packet-mark="" parent=ether1 priority=8 queue=qos
add burst-limit=0 burst-threshold=0 burst-time=0s disabled=no limit-at=0 max-limit=0 name=queue-wan1-bulk packet-mark=bulk,no-mark parent=queue-wan1-root priority=8 queue=qos
add burst-limit=0 burst-threshold=0 burst-time=0s disabled=no limit-at=0 max-limit=0 name=queue-wan1-voice packet-mark=voice parent=queue-wan1-root priority=1 queue=qos
add burst-limit=0 burst-threshold=0 burst-time=0s disabled=no limit-at=0 max-limit=0 name=queue-wan1-priority-data packet-mark=priority-data parent=queue-wan1-root priority=7 queue=qos

/queue tree
add burst-limit=0 burst-threshold=0 burst-time=0s disabled=no limit-at=0 max-limit=0 name=queue-wan2-root packet-mark="" parent=ether2 priority=8 queue=qos
add burst-limit=0 burst-threshold=0 burst-time=0s disabled=no limit-at=0 max-limit=0 name=queue-wan2-bulk packet-mark=bulk,no-mark parent=queue-wan2-root priority=8 queue=qos
add burst-limit=0 burst-threshold=0 burst-time=0s disabled=no limit-at=0 max-limit=0 name=queue-wan2-voice packet-mark=voice parent=queue-wan2-root priority=1 queue=qos
add burst-limit=0 burst-threshold=0 burst-time=0s disabled=no limit-at=0 max-limit=0 name=queue-wan2-priority-data packet-mark=priority-data parent=queue-wan2-root priority=7 queue=qos

Best Effort Queuing with Global-In and Guaranteed Queuing on outbound with two ISPs and connection tracking

This sets up three root queues: queue-root-global-in (best effort), queue-root-isp1 (guaranteed), queue-root-isp2 (guaranteed).

queue-root-global-in deals with traffic coming from the internet to the router and catches traffic that is bound for the router itself as well as traffic that will be forwarded through to NAT clients. There is a subqueue for each isp.

queue-root-isp1 deals with the outbound traffic to isp1 from the router as well as NAT clients behind the router.

queue-root-isp2 deals with the outbound traffic to isp2 from the router as well as NAT clients behind the router.

The traffic that is handled in these last two queues is marked by the mark-traffic mangle chain. The packet marks are generic to the type of traffic (bulk, priority, voice) because the root queues are connected to specific interfaces.

The traffic that is handled in the global-in queue is marked by rules in the prerouting chain. The packets marks are specific to the interface that the traffic came in on (isp1-bulk, isp2-bulk, etc) because the root queue is not connected to a specific interface but to global-in, which is an aggregate of ALL incoming packets on ALL interfaces.

/queue type
add kind=sfq name=qos sfq-allot=1514 sfq-perturb=5

/queue tree
add burst-limit=0 burst-threshold=0 burst-time=0s comment="isp1 outbound queue" disabled=no limit-at=128k max-limit=100M name=queue-root-isp1 parent=ether1 priority=8 queue=qos
add burst-limit=0 burst-threshold=0 burst-time=0s disabled=no limit-at=128k max-limit=100M name=isp1-bulk packet-mark=bulk,no-mark parent=queue-root-isp1 priority=8 queue=qos
add burst-limit=0 burst-threshold=0 burst-time=0s disabled=no limit-at=128k max-limit=100M name=isp1-priority packet-mark=priority parent=queue-root-isp1 priority=7 queue=qos
add burst-limit=0 burst-threshold=0 burst-time=0s disabled=no limit-at=128k max-limit=100M name=isp1-voice packet-mark=voice parent=queue-root-isp1 priority=1 queue=qos

add burst-limit=0 burst-threshold=0 burst-time=0s comment="isp2 outbound queue" disabled=no limit-at=128k max-limit=100M name=queue-root-isp2 parent=ether2 priority=8 queue=qos
add burst-limit=0 burst-threshold=0 burst-time=0s disabled=no limit-at=128k max-limit=100M name=isp2-bulk packet-mark=bulk,no-mark parent=queue-root-isp2 priority=8 queue=qos
add burst-limit=0 burst-threshold=0 burst-time=0s disabled=no limit-at=128k max-limit=100M name=isp2-priority packet-mark=priority parent=queue-root-isp2 priority=7 queue=qos
add burst-limit=0 burst-threshold=0 burst-time=0s disabled=no limit-at=128k max-limit=100M name=isp2-voice packet-mark=voice parent=queue-root-isp2 priority=1 queue=qos

add burst-limit=0 burst-threshold=0 burst-time=0s disabled=no limit-at=0 max-limit=0 name=global-in-root parent=global-in priority=8 comment="inbound queue root" queue=qos

add burst-limit=0 burst-threshold=0 burst-time=0s disabled=no limit-at=128k max-limit=100M name=isp1-from-internet-root parent=global-in-root priority=8 comment="isp1 inbound queue" queue=qos
add burst-limit=0 burst-threshold=0 burst-time=0s disabled=no limit-at=128k max-limit=100M name=isp1-fi-bulk packet-mark=isp1-fi-bulk parent=isp1-from-internet-root priority=8 queue=qos
add burst-limit=0 burst-threshold=0 burst-time=0s disabled=no limit-at=128k max-limit=100M name=isp1-fi-priority packet-mark=isp1-fi-priority parent=isp1-from-internet-root priority=7 queue=qos
add burst-limit=0 burst-threshold=0 burst-time=0s disabled=no limit-at=128k max-limit=100M name=isp1-fi-voice packet-mark=isp1-fi-voice parent=isp1-from-internet-root priority=1 queue=qos

add burst-limit=0 burst-threshold=0 burst-time=0s disabled=no limit-at=128k max-limit=100M name=isp2-from-internet-root parent=global-in-root priority=8 comment="isp2 inbound queue" queue=qos
add burst-limit=0 burst-threshold=0 burst-time=0s disabled=no limit-at=128k max-limit=100M name=isp2-fi-bulk packet-mark=isp2-fi-bulk parent=isp2-from-internet-root priority=8 queue=qos
add burst-limit=0 burst-threshold=0 burst-time=0s disabled=no limit-at=128k max-limit=100M name=isp2-fi-priority packet-mark=isp2-fi-priority parent=isp2-from-internet-root priority=7 queue=qos
add burst-limit=0 burst-threshold=0 burst-time=0s disabled=no limit-at=128k max-limit=100M name=isp2-fi-voice packet-mark=isp2-fi-voice parent=isp2-from-internet-root priority=1 queue=qos

These are the associated firewall rules:

/ip firewall mangle
add action=jump chain=prerouting comment="Jump to mark-traffic: MUST RETURN FROM THIS JUMP" disabled=no jump-target=mark-traffic

add action=mark-packet chain=prerouting comment="isp1 global-in packet marking - bulk" disabled=no in-interface=ether1 new-packet-mark=isp1-fi-bulk passthrough=yes
add action=mark-packet chain=prerouting comment="isp1 global-in voice" disabled=no in-interface=ether1 new-packet-mark=isp1-fi-voice passthrough=no src-address-list=voice-servers
add action=mark-packet chain=prerouting comment="isp1 global-in priority placeholder" disabled=yes in-interface=ether1 new-packet-mark=isp1-fi-priority passthrough=no src-address=1.1.1.1

add action=mark-packet chain=prerouting comment="isp2 global-in packet marking - bulk" disabled=no in-interface=ether2 new-packet-mark=isp2-fi-bulk passthrough=yes
add action=mark-packet chain=prerouting comment="isp2 global-in voice" disabled=no in-interface=ether2 new-packet-mark=isp2-fi-voice passthrough=no src-address-list=voice-servers
add action=mark-packet chain=prerouting comment="isp2 global-in priority placeholder" disabled=yes in-interface=ether2 new-packet-mark=isp2-fi-priority passthrough=no src-address=2.2.2.2

add action=jump chain=output comment="Jump to mark-traffic: MUST RETURN FROM THIS JUMP" disabled=no jump-target=mark-traffic packet-mark=no-mark

add action=mark-connection chain=prerouting comment="isp1 connection tracking" connection-state=new disabled=no in-interface=ether1 new-connection-mark=isp1 passthrough=no
add action=mark-routing chain=prerouting connection-mark=isp1 disabled=no in-interface=!ether1 new-routing-mark=isp1 passthrough=no
add action=mark-routing chain=output connection-mark=isp1 disabled=no new-routing-mark=isp1 passthrough=no src-address=1.1.1.0/30

add action=mark-connection chain=prerouting comment="isp2 connection tracking" connection-state=new disabled=no in-interface=ether2 new-connection-mark=isp2 passthrough=no
add action=mark-routing chain=prerouting connection-mark=isp2 disabled=no in-interface=!ether2 new-routing-mark=isp2 passthrough=no
add action=mark-routing chain=output connection-mark=isp2 disabled=no new-routing-mark=isp2 passthrough=no src-address=2.2.2.0/30

add action=mark-packet chain=mark-traffic comment="mark-traffic: default mark bulk" disabled=no new-packet-mark=bulk passthrough=yes
add action=mark-packet chain=mark-traffic comment="mark-traffic: voice" disabled=no dst-address-list=voice-servers new-packet-mark=voice passthrough=yes
add action=return chain=mark-traffic disabled=no packet-mark=voice

add action=mark-packet chain=mark-traffic comment="mark-traffic: priority (placeholder)" disabled=yes dst-address=1.1.1.1 new-packet-mark=priority passthrough=yes
add action=return chain=mark-traffic disabled=yes packet-mark=priority

add action=return chain=mark-traffic comment="mark-traffic: end of chain return" disabled=no

These are the needed routing table entries:

/ip route
add check-gateway=ping comment="default route via isp1" disabled=no distance=1 dst-address=0.0.0.0/0 gateway=1.1.1.1
add check-gateway=ping comment="default route via isp2" disabled=no distance=20 dst-address=0.0.0.0/0 gateway=2.2.2.1

add check-gateway=ping comment="isp1 default route" disabled=no dst-address=0.0.0.0/0 gateway=1.1.1.1 routing-mark=isp1
add check-gateway=ping comment="isp2 default route" disabled=no dst-address=0.0.0.0/0 gateway=2.2.2.1 routing-mark=isp2

Enable/Disable Services

/ ip service 
set telnet port=23 address=0.0.0.0/0 disabled=no 
set ftp port=21 address=0.0.0.0/0 disabled=yes 
set www port=80 address=0.0.0.0/0 disabled=yes 
set ssh port=22 address=0.0.0.0/0 disabled=no 
set www-ssl port=443 address=0.0.0.0/0 certificate=none disabled=yes 

Enable/Disable Service Ports

/ ip firewall service-port 
set ftp ports=21 disabled=no 
set tftp ports=69 disabled=no 
set irc ports=6667 disabled=no 
set h323 disabled=yes 
set quake3 disabled=no 
set gre disabled=no 
set pptp disabled=no 

Initial SNMP configuration

This simply turns on SNMP and sets the public community to only be access from localhost, which renders it useless.

Add your own community entry to make use of SNMP, but I recommend not deleting the public entry. This is due to an issue in the Mikrotik export function for SNMP.

/snmp
set contact="" enabled=yes engine-boots=0 engine-id="" location="" \
    time-window=15 trap-sink=0.0.0.0 trap-version=1
/snmp community
set public address=127.0.0.1/32 authentication-password="" \
    authentication-protocol=MD5 encryption-password="" encryption-protocol=\
    DES name=public read-access=yes security=none write-access=no

For v5:

/snmp
set contact="" enabled=yes engine-id="" location="" trap-community=public \
    trap-target=0.0.0.0 trap-version=1
/snmp community
set public address=127.0.0.1/32 authentication-password="" \
    authentication-protocol=MD5 encryption-password="" encryption-protocol=DES \
    name=public read-access=yes security=none write-access=no
add address=0.0.0.0/0 authentication-password="" authentication-protocol=MD5 \
    encryption-password="" encryption-protocol=DES name=localmon read-access=\
    yes security=none write-access=no
/snmp
set contact="" enabled=yes engine-id="" location="" trap-community=public \
    trap-target=0.0.0.0 trap-version=1

Protecting your LAN

/ip firewall filter
add chain=protect-lan connection-state=invalid action=drop comment="Drop invalid packets" disabled=no 
add chain=protect-lan connection-state=established action=accept comment="Allow established traffic to pass" disabled=no 
add chain=protect-lan connection-state=related action=accept comment="Allow related traffic to pass" disabled=no 
add chain=protect-lan action=drop comment="Drop everything else" disabled=no

Private IP Subnet address lists

/ip firewall address-list
add address=10.0.0.0/8 comment="private 10.0.0.0/255.0.0.0" disabled=no list=private-ip-subnets
add address=172.16.0.0/12 comment="private 172.16.0.0/255.240.0.0" disabled=no list=private-ip-subnets
add address=192.168.0.0/16 comment="private 192.168.0.0/255.255.0.0" disabled=no list=private-ip-subnets

Dealing with SPAM from inside

These rules will allow outbound smtp from valid internal addresses, block it from ip's that we think are compromised, and track connections to determine if we think an ip is compromised.

/ip firewall filter
add action=jump chain=forward comment="Push outbound SMTP traffic to the filter-smtp chain - check address lists for banned IPs" disabled=no dst-port=25 \
    in-interface=bridge-lan jump-target=filter-smtp protocol=tcp

/ip firewall filter
add action=drop chain=filter-smtp comment="SPAM: Block SMTP from blacklist static list" disabled=no \
    src-address-list=smtp-possible-spammers-static
add action=add-src-to-address-list address-list=smtp-possible-spammers address-list-timeout=9m \
    chain=filter-smtp comment=\
    "SPAM: Test outbound connections for connection limit for whitelisted limit" connection-limit=\
    10,32 disabled=no dst-port=25 protocol=tcp src-address-list=smtp-allowed-outbound
add action=add-src-to-address-list address-list=smtp-possible-spammers address-list-timeout=9m \
    chain=filter-smtp comment="SPAM: Test outbound connections for connection limit" \
    connection-limit=2,32 disabled=no dst-port=25 protocol=tcp src-address-list=\
    !smtp-allowed-outbound
add action=log chain=filter-smtp comment="SPAM: Log SMTP from blacklist" disabled=no log-prefix=\
    possible-spammer src-address-list=smtp-possible-spammers
add action=drop chain=filter-smtp comment="SPAM: Block SMTP from blacklist" disabled=no \
    src-address-list=smtp-possible-spammers
add action=return chain=filter-smtp comment="SPAM: Good packet... return" disabled=no

Once this is done, we need to be notified somehow. This is done with a script that runs every X minutes. The script sends a single email for each address listed. Add addresses to the smtp-possible-spammers list for 10 minutes (or more) and run this scripts every 5 minutes.

:local spamadmin notify@change.me

:local count 0
:local message ""
:local tmp

:foreach i in=[/ip firewall address-list find list=smtp-possible-spammers] \
do={ \
:set count ($count + 1)
:set tmp ([/ip firewall address-list get $i address])
:set message ($message . $tmp . "\r\n")
:log warning ("possible spammfer found at " . $tmp)
}

:if ($count > 0) \
do={ \
:log info "watch-for-spammers sending notification"
/tool e-mail send \
     to=$spamadmin \
     subject=([/system identity get name] . ": $count possible spammers found") \
     body=$message
}

Paste the following in a terminal to create the above script:

/system script
add name=watch-for-spammers source=":local spamadmin notify@change.me\r\
    \n\r\
    \n:local count 0\r\
    \n:local message \"\"\r\
    \n:local tmp\r\
    \n\r\
    \n:foreach i in=[/ip firewall address-list find list=smtp-possible-spammer\
    s] \\\r\
    \ndo={ \\\r\
    \n:set count (\$count + 1)\r\
    \n:set tmp ([/ip firewall address-list get \$i address])\r\
    \n:set message (\$message . \$tmp . \"\\r\\n\")\r\
    \n:log warning (\"possible spammfer found at \" . \$tmp)\r\
    \n}\r\
    \n\r\
    \n:if (\$count > 0) \\\r\
    \ndo={ \\\r\
    \n:log info \"watch-for-spammers sending notification\"\r\
    \n/tool e-mail send \\\r\
    \n     to=\$spamadmin \\\r\
    \n     subject=([/system identity get name] . \": \$count possible spammer\
    s found\") \\\r\
    \n     body=\$message\r\
    \n}\r\
    \n"

This is the line to add the scheduled task.

/system scheduler 
add comment="" disabled=no interval=5m name="check-spammer-list" on-event="/system script run watch-for-spammers" \
    start-date=jan/01/1970 start-time=00:00:00 

An alternate method is to allow outbound SMTP only from a specified list of IP's. The rules below allow outbound SMTP from addresses on the list smtp-allowed-outbound, and logs all other tries to smtp-possible-spammers followed by the drop.

/ ip firewall filter 
add chain=lan-forward-out action=accept dst-port=25 protocol=tcp \
    src-address-list=smtp-allowed-outbound comment="SPAM: Allow traffic from \
    whitelist" disabled=no 
add chain=lan-forward-out action=add-src-to-address-list dst-port=25 \
    protocol=tcp address-list=smtp-possible-spammers address-list-timeout=0s \
    comment="Log all other outbound SMTP" disabled=no 
add chain=lan-forward-out action=drop dst-port=25 protocol=tcp comment="Drop \
    all other outbound SMTP" disabled=no 

Scripting

# list addresses in visisted-mailserver address-list
/ip firewall address-list
:foreach i in [find list=visited-mailservers ] do={:put [get $i address]}

Automated Backups

Make sure you change the smtpserver value to a valid SMTP server for your Internet connection.

:log info "backup Beginning now"
:local toaddress systembackup@change.me

:global subject ([/system identity get name] . " Backup " . [/system clock get time])
:global backupfile ([/system identity get name] . "_backup")

:log info "backup Backing up config"
/export file="$backupfile"

:log info "backup pausing for 3s"
:delay 3s

:log info "backup being emailed"
/tool e-mail send to=$toaddress subject=$subject file="$backupfile.rsc"

:log info "backup finished"

Paste the following in a terminal to create the above script:

/system script
add name=backup-router policy=ftp,reboot,read,write,policy,test,winbox,password,sniff,sensitive source=":log info \"backup\
    \_Beginning now\"\r\
    \n:local toaddress systembackup@change.me\r\
    \n\r\
    \n:global subject ([/system identity get name] . \" Backup \" . [/system clock get time])\r\
    \n:global backupfile ([/system identity get name] . \"_backup\")\r\
    \n\r\
    \n:log info \"backup Backing up config\"\r\
    \n/export file=\"\$backupfile\"\r\
    \n\r\
    \n:log info \"backup pausing for 3s\"\r\
    \n:delay 3s\r\
    \n\r\
    \n:log info \"backup being emailed\"\r\
    \n/tool e-mail send to=\$toaddress subject=\$subject file=\"\$backupfile.rsc\"\r\
    \n\r\
    \n:log info \"backup finished\"\r\
    \n"

If you want the router to automatically email you the backup on an interval use the following script:

/system scheduler
add disabled=no interval=1w name=backup-router-weekly on-event="/system script run backup-router \r\n"\
    start-date=jan/01/2012 start-time=01:00:00

Netwatching

This will send an email on up and down:

/tool netwatch
add comment="some-device" disabled=no \
	down-script="/tool e-mail send to=email@domain.com subject=\"some-device down\"" \
	up-script="/tool e-mail send to=email@domain.com subject=\"some-device up\"" \
	host=1.1.1.1

This will add an entry to the log on up and down:

/tool netwatch
add comment="some-device" disabled=no \
    down-script="/log warning message=\"some-device down\"" \
    up-script="/log warning message=\"some-device up\"" \
    host=1.1.1.1 interval=10s timeout=1s 

Clearing the arp cache

The following script will clear the arp cache every $delaytime a total of $numloops times.

:log info "clearing arp table of dynamic entries"
:local counter 0
:local delaytime 5
:local numloops 12

:while ($counter < $numloops) do={ \

:log info "clearing arp loop"

:foreach i in=[/ip arp find dynamic=yes] do={ \
/ip arp remove $i
}

:log info "delaying..."

:delay $delaytime

}

Clear connections

This script clears the connection tracking table:

add name=clear-connections policy=ftp,reboot,read,write,policy,test,winbox,password,sniff,sensitive,api source=":log info message=\"clearing connections begin\
    \"\r\
    \n:foreach i in=[/ip firewall connection find] do={/ip firewall connection remove \$i}\r\
    \n:log info message=\"clearing connections end\"\r\
    \n"

Upgrade timing

RouterBoard 150

RouterOS 2.9.46, BIOS 2.9 -> RouterOS 2.9.51: 2m45s
RouterOS 2.9.51, BIOS 2.9 -> BIOS 2.12: 30s

RouterOS 2.9.51, BIOS 2.12 -> RouterOS 3.9: 2m5s
RouterOS 2.9.51, BIOS 2.12 -> RouterOS 3.9: 2m0s

RouterOS 3.9, BIOS 2.12 -> BIOS 2.14: 28s
RouterOS 3.9, BIOS 2.12 -> BIOS 2.14: 28s

OSPF Route Filtering

/routing filter
add action=accept chain=ospf-private-only-out comment="" disabled=no invert-match=no prefix=10.0.0.0/8 prefix-length=8-32
add action=accept chain=ospf-private-only-out comment="" disabled=no invert-match=no prefix=172.16.0.0/12 prefix-length=12-32
add action=accept chain=ospf-private-only-out comment="" disabled=no invert-match=no prefix=192.168.0.0/16 prefix-length=16-32
add action=discard chain=ospf-private-only-out comment="" disabled=no invert-match=no
add action=accept chain=ospf-private-only-in comment="" disabled=no invert-match=no prefix=10.0.0.0/8 prefix-length=8-32
add action=accept chain=ospf-private-only-in comment="" disabled=no invert-match=no prefix=172.16.0.0/12 prefix-length=12-32
add action=accept chain=ospf-private-only-in comment="" disabled=no invert-match=no prefix=192.168.0.0/16 prefix-length=16-32
add action=discard chain=ospf-private-only-in comment="" disabled=no invert-match=no

/routing ospf instance
set [ find default=yes ] disabled=no distribute-default=never in-filter=ospf-private-only-in metric-bgp=auto metric-connected=20 metric-default=1 \
    metric-other-ospf=auto metric-rip=20 metric-static=20 name=default out-filter=ospf-private-only-out redistribute-bgp=no redistribute-connected=as-type-1 \
    redistribute-other-ospf=no redistribute-rip=no redistribute-static=no router-id=0.0.0.0

Hotspot and Apple IOS

http://forum.mikrotik.com/viewtopic.php?f=2&t=42942 http://www.cadincweb.com/why-your-apple-ios-7-device-wont-connect-to-the-wifi-network

/ip hotspot profile set hsprof1 dns-name=""
/ip hotspot walled-garden
add action=allow disabled=no dst-host=www.appleiphonecell.com dst-port=""
add action=allow disabled=no dst-host=captive.apple.com dst-port=""
add action=allow disabled=no dst-host=www.apple.com dst-port="" path=/library/test/success.html

Send email with attached log on startup

/system scheduler
add comment="" disabled=no interval=0s name=startup-notify on-event="/log print file=mikrotik.log.txt\r\
    \n/tool e-mail send to=notify@change.me subject=\"\$[/system identity get name] startup at \$[/system c\
    lock get time] \$[/system clock get date]\" body=\"See attached log file\" file=mikrotik.log.txt\r\
    \n" policy=ftp,reboot,read,write,policy,test,winbox,password,sniff,sensitive start-time=startup

Private use MAC addresses

The following range of MAC Addresses are reserved for private use:

AC:DE:48:00:00:00 to AC:DE:48:FF:FF:FF

When creating bridge interfaces on the Mikrotik router, create an admin MAC address using this range. I pull the last three octets from an actual device on the router.

Pantech UML290

What makes it work is the phone number: *99***3#

/interface ppp-client
add add-default-route=yes allow=pap,chap,mschap1,mschap2 data-channel=0 \
    dial-command=ATDT dial-on-demand=no disabled=no info-channel=0 \
    keepalive-timeout=30 max-mru=1500 max-mtu=1500 modem-init="" mrru=disabled \
    name=ppp-out1 null-modem=no password="" phone=*99***3# pin="" port=usb2 \
    profile=default use-peer-dns=yes user=""

 

/interface ppp-client
set 0 add-default-route=yes allow=pap,chap,mschap1,mschap2 data-channel=0 \
    dial-command=ATDT dial-on-demand=no disabled=no info-channel=0 \
    keepalive-timeout=30 max-mru=1500 max-mtu=1500 modem-init="" mrru=disabled \
    name=ppp-out-cellular null-modem=no password="" phone=*99***3# pin="" port=usb2 \
    profile=default use-peer-dns=yes user=""

Sprint - Sierra Wireless 598

/interface ppp-client
add add-default-route=yes allow=pap,chap,mschap1,mschap2 data-channel=0 \
    dial-command=ATDT dial-on-demand=no disabled=no info-channel=0 \
    keepalive-timeout=30 max-mru=1500 max-mtu=1500 modem-init="" mrru=disabled \
    name=ppp-out-cellular null-modem=no password="" phone=#777 pin="" port=usb1 \
    profile=default use-peer-dns=no user=""

IPSEC/IPIP/MSS Mangling

MSS should be set to 40 bytes less than the MTU of the circuit.
All caluclations below assume a circuit the supports a maximum MTU of 1500 bytes.
If these tunnels traverse a PPPoE circuit, an additional 20 bytes would need to be subtracted for PPPoE overhead.

IPSEC Max Overhead: 58 bytes (depends on tunnel/transport/ESP/AH)
IPIP Tunnel Overhead: 50 bytes
GRE Tunnel Overhead: 24 bytes
EOIP Tunnel Overhead: None due to fragmentation support

MSS without IPSEC:
GRE Tunnel  : MTU 1476 - 40 = MSS 1436 (If MSS >= 1437-65535, change mss to 1436)
IPIP Tunnel : MTU 1450 - 40 = MSS 1410 (If MSS >= 1411-65535, change mss to 1410)

MSS with IPSEC:
EOIP Tunnel :
GRE Tunnel  : MTU 1476 - 58 - 40 = MSS 1378 (If MSS >= 1379-65535, change mss to 1378)
IPIP Tunnel : MTU 1450 - 58 - 40 = MSS 1352 (If MSS >= 1353-65535, change mss to 1352)

To be on the safe side, you could just create a generic rule to change the MSS down to 1350. Doing this sacrifices anywhere from 2 to 86 bytes, but covers your bases for all tunnel types.

Rule sets for MSS 1350

/ip firewall mangle
add action=jump chain=forward in-interface=ipip- jump-target=change-mss
add action=jump chain=forward out-interface=ipip- jump-target=change-mss

add action=change-mss chain=change-mss comment="change-mss - change TCP MSS - currently set to 1350" new-mss=1350 passthrough=no protocol=tcp tcp-flags=syn tcp-mss=1351-65535
add action=return chain=change-mss comment="change-mss - return - enable if needed - delete if not" disabled=yes


Rule sets for MSS 1330

/ip firewall mangle
add action=jump chain=forward in-interface=ipip- jump-target=change-mss
add action=jump chain=forward out-interface=ipip- jump-target=change-mss

add action=change-mss chain=change-mss comment="change-mss - change TCP MSS - currently set to 1330" new-mss=1330 passthrough=no protocol=tcp tcp-flags=syn tcp-mss=1331-65535
add action=return chain=change-mss comment="change-mss - return - enable if needed - delete if not" disabled=yes


Rule sets for MSS 1300

/ip firewall mangle
add action=jump chain=forward in-interface=ipip- jump-target=change-mss
add action=jump chain=forward out-interface=ipip- jump-target=change-mss

add action=change-mss chain=change-mss comment="change-mss - change TCP MSS - currently set to 1300" new-mss=1300 passthrough=no protocol=tcp tcp-flags=syn tcp-mss=1301-65535
add action=return chain=change-mss comment="change-mss - return - enable if needed - delete if not" disabled=yes


DNS Changer IP Subnets

/ip firewall address-list
add address=85.255.112.0/20 disabled=no list=DNSchanger
add address=67.210.0.0/20 disabled=no list=DNSchanger
add address=93.188.160.0/21 disabled=no list=DNSchanger
add address=77.67.83.0/24 disabled=no list=DNSchanger
add address=213.109.64.0/20 disabled=no list=DNSchanger
add address=64.28.176.0/20 disabled=no list=DNSchanger

Web proxy blocking IP based URLs

I had a customer today tell me that their Barracuda web filter is the only device they've found to date that can do IP based URL blocking. He said he knows Fortinet and Sonicwall can't do it, and Palo Alto said they might be able to. The proxy access rule below for the Mikrotik does just that:

/ip proxy access
add action=deny dst-host=":([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})"

Writing files in RouterOS

Source

Notes:

The basic commands for working with a file, using variables in place of static content or file names:
1. To create a new file
/file print file=$filename

2. To read an existing file
:set $filedata [/file get $filename contents]

3. To write to an existing file
/file set $filename contents=$newdata

4. To append to an existing file
/file set $filename contents=([get $filename contents] . $newdata)
Networking

Netplan, Bonding, and VLANs

Example

This is a configuration using from Ubuntu 18.04 LTS. Two Ethernet interfaces are bonded using the active-passive mode. The untagged bond0 interface is for private traffic, while a public IP address is being delivered to a tagged VLAN sub interface using VLAN 262.

The following packages are required: ifenslave and vlan

network:
  version: 2
  ethernets:
    enp1s0:
      optional: true
    enp2s0:
      optional: true
  bonds:
    bond0:
      addresses: [ 192.168.168.115/24 ]
      interfaces: [ enp1s0, enp2s0 ]
      parameters:
        mode: active-backup
        primary: enp1s0
        mii-monitor-interval: 100
        up-delay: 10000 # must be a multiple of mii-monitor-interval
#      routes:
#        - to: 10.0.0.0/8
#          via: 192.168.168.1
#        - to: 172.16.0.0/12
#          via: 192.168.168.1
#        - to: 192.168.0.0/16
#          via: 192.168.168.1
  vlans:
    bond0.262:
        id: 262
        link: bond0
        addresses: [ 1.1.1.123/28 ]
        gateway4: 1.1.1.113
        nameservers:
            search: [ servers.domain.com ]
            addresses:
            - "8.8.8.8"
            - "8.8.4.4"

References

 

Networking

OSPF

OSPF Cost

cost = reference bandwidth / configured bandwidth of interface in kbps

reference bandwidth = 100,000 kbps
Interface speed OSPF Cost
100 Mbps 1
10 Mbps 10
6 Mbps 17
5 Mbps 20
4 Mbps 25
3 Mbps 33
2 Mbps 50
1.5 Mbps 67
1 Mbps 100
768 Kbps 130
512 Kbps 195
384 Kbps 260
256 Kbps 391
128 Kbps 781
64 Kbps 1563

 

Networking

Ruckus / Brocade

https://robrobstation.com/2017/07/17/ruckus-icx7150-c12p-initial-configuration/

 

Networking

The Most Common OpenSSL Commands

General OpenSSL Commands

These commands allow you to generate CSRs, Certificates, Private Keys and do other miscellaneous tasks.

Generate a new private key and Certificate Signing Request

openssl req -out CSR.csr -new -newkey rsa:2048 -nodes -keyout privateKey.key

Generate a self-signed certificate (see How to Create and Install an Apache Self Signed Certificate for more info)

openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout privateKey.key -out certificate.crt

Generate a certificate signing request (CSR) for an existing private key

openssl req -out CSR.csr -key privateKey.key -new

Generate a certificate signing request based on an existing certificate

openssl x509 -x509toreq -in certificate.crt -out CSR.csr -signkey privateKey.key

Remove a passphrase from a private key

openssl rsa -in privateKey.pem -out newPrivateKey.pem

Checking Using OpenSSL

If you need to check the information within a Certificate, CSR or Private Key, use these commands. You can also check CSRs and check certificates using our online tools.

Check a Certificate Signing Request (CSR)

openssl req -text -noout -verify -in CSR.csr

Check a private key

openssl rsa -in privateKey.key -check

Check a certificate

openssl x509 -in certificate.crt -text -noout

Check a PKCS#12 file (.pfx or .p12)

openssl pkcs12 -info -in keyStore.p12

Debugging Using OpenSSL

If you are receiving an error that the private doesn't match the certificate or that a certificate that you installed to a site is not trusted, try one of these commands. If you are trying to verify that an SSL certificate is installed correctly, be sure to check out the SSL Checker.

Check an MD5 hash of the public key to ensure that it matches with what is in a CSR or private key

openssl x509 -noout -modulus -in certificate.crt | openssl md5
openssl rsa -noout -modulus -in privateKey.key | openssl md5
openssl req -noout -modulus -in CSR.csr | openssl md5

Check an SSL connection. All the certificates (including Intermediates) should be displayed

openssl s_client -connect www.paypal.com:443

Converting Using OpenSSL

These commands allow you to convert certificates and keys to different formats to make them compatible with specific types of servers or software. For example, you can convert a normal PEM file that would work with Apache to a PFX (PKCS#12) file and use it with Tomcat or IIS. Use our SSL Converter to convert certificates without messing with OpenSSL.

Convert a DER file (.crt .cer .der) to PEM

openssl x509 -inform der -in certificate.cer -out certificate.pem

Convert a PEM file to DER

openssl x509 -outform der -in certificate.pem -out certificate.der

Convert a PKCS#12 file (.pfx .p12) containing a private key and certificates to PEM

openssl pkcs12 -in keyStore.pfx -out keyStore.pem -nodes

You can add -nocerts to only output the private key or add -nokeys to only output the certificates.

Convert a PEM certificate file and a private key to PKCS#12 (.pfx .p12)

openssl pkcs12 -export -out certificate.pfx -inkey privateKey.key -in certificate.crt -certfile CACert.crt

Source

Networking

Wireshark

Capture Filters

bootp and dhcp

Source

port 67 or port 68

Name resolution protocols

DNS

Cisco Discovery Protocol

udp port 53

mDNS

udp port 5353

LLMNR

Link-local multicast name resolution

udp port 5355

All together now

udp port 53 or udp port 5353 or udp port 5355

Network discovery protocols

An easy way to view discovery protocol traffic from a laptop is by using Wireshark and the capture filters below for CDP, LLDP and MNDP. Use the appropriate capture filter for the type of device you're trying to gather information about, or use all three of them in the same capture filter.

CDP

Cisco Discovery Protocol

ether host 01:00:0c:cc:cc:cc and ether[16:4] = 0x0300000C and ether[20:2] == 0x2000

LLDP

ether proto 0x88cc

MNDP

Mikrotik Discovery Protocol

udp dst port 5678 and udp src port 5678

CDP/LLDP/MNDP

All three of the above capture filters in one:

(ether host 01:00:0c:cc:cc:cc and ether[16:4] = 0x0300000C and ether[20:2] == 0x2000) or (ether proto 0x88cc) or (udp dst port 5678 and udp src port 5678)

Capturing on an interval in Linux

The command below will capture all traffic to/from 8.8.8.8. A new capture file will be created every 600 seconds (10 minutes).

dumpcap -b duration:600 -f "host 8.8.8.8" -w capture-google

Mikrotik Packet Capture Streaming

To accept only TZSP traffic, Capture Filter like this can be used:

udp port 37008

Note that TZSP can be sent on any UDP port you set it to, so adjust the above capture as needed.

Using tshark

Interface List

This is typically needed when running tshark on Windows.

tshark -D
thsark -i <interface_id>

Capture Filter

# capture only udp dns packets
tshark -f "udp port 53"

Saving Packets

# save packets (doesn't display packets)
tsharp -f "udp port 37008" -w captured.pcap

# save and display packets
tsharp -f "udp port 37008" -w captured.pcap -P

# save and display packets with LOTS of detail
tsharp -f "udp port 37008" -w captured.pcap -P -O dns -V

Automatic stop

Options are duration:[seconds], filesize:[KB], and files:[n].

tshark -a duration:60
tshark -a filesize:1000

Ring Buffer Capture

tshark -b duration:3600 -b filesize:1000 -b files:24 -w ring_buffer.pcap
tshark -b duration:86400 -b filesize:1000 -b files:30 -w ring_buffer.pcap

Practical examples

# TZSP stream capture on specific interface
tshark -f "udp port 37008" -i 5

# TZSP stream capture on alternate udp port, uses decode as feature
tshark -f "udp port 37091" -d udp.port==37091,tzsp

NodeJS

Process Management

ORM for Node.js

Template languages for JavaScript

Other useful modules for Node.js

OpenVPN

Integrating OpenVPN with RADIUS

Below is the culmination of at least a month working with OpenVPN.

I recently discovered a bug in the Mikrotik OpenVPN server implementation that prevents users from being informed that they entered invalid credentials. The project I'm working on requires Azure MFA be part of the equation for VPN authentication. Azure ADDS will lock a user account after three failed authentication attempts. Because of this Mikrotik bug, a user who accidentally mistypes a password has about nine seconds before Azure ADDS locks there account.

I submitted a bug report to Mikrotik, but as much as I am a Mikrotik fanboy, I know they're not going to fix it anytime soon. I can't be the first person to inform them of this bug, and their OpenVPN Server has always left much to be desired and not gotten much attention from them, so I don't expect it to be resolved anytime soon. I also wasn't about to deploy a VPN solution to a user base that was going to be constantly resulting in calls to the helpdesk because of account lockouts.

I wanted to use the Mikrotik router because its very easy to implement firewall policies that are based on the user's group returned from RADIUS.

The goal of this exercise was learning how to integrate RADIUS authentication with MFA PUSH requirements and be able to service similar group based firewall policies.

I have not yet gotten around to the firewall policies implementation, but I do have a good idea of how it can be done.

The configuration below uses the following authentication strategies:

If you know what you're doing, you can use the configs and scripts listed below to implement the same solution. 👍

Installing OpenVPN and the OpenVPN plugin in Ubuntu

apt -y install openvpn openvpn-auth-radius

mkdir -p /etc/openvpn/client /etc/openvpn/server /etc/openvpn/server-ccd

/etc/openvpn/server/server.conf

server 192.168.168.0 255.255.255.0
proto udp
port 1194
dev tun1
topology subnet

;user nobody
;group nogroup

cipher AES-256-GCM
auth SHA256

management 127.0.0.1 61194 /etc/openvpn/server/pw-file

duplicate-cn

;client-cert-not-required
verify-client-cert require
;username-as-common-name

;auth-user-pass-verify

ca         /etc/openvpn/server/ca-chained.crt
crl-verify /etc/openvpn/server/crl.pem
cert       /etc/openvpn/server/server.crt
key        /etc/openvpn/server/server.key
dh none
ecdh-curve secp521r1

tls-auth /etc/openvpn/server/tls-auth.key 0
tls-version-min 1.2

verb 1
log               /var/log/openvpn/openvpn.log
status            /var/log/openvpn/openvpn-status.log
client-config-dir /etc/openvpn/server-ccd

script-security 2
tls-verify            /etc/openvpn/server/script-tls-verify
auth-user-pass-verify /etc/openvpn/server/script-auth-user-pass-verify via-env
plugin                /usr/lib/openvpn/radiusplugin.so /etc/openvpn/server/radiusplugin.cnf
client-connect        /etc/openvpn/server/script-client-connect

ifconfig-pool-persist /var/log/openvpn/ipp.txt

push "dhcp-option DNS 8.8.8.8"
push "dhcp-option DNS 8.8.4.4"
;push "dhcp-option DNS 192.168.168.1"
push "dhcp-option DOMAIN ovpn.loc"

; Do you want the default gateway redirected? There's a few ways to do it:
push "redirect-gateway"
;push "redirect-gateway autolocal"
;push "redirect-gateway def1 bypass-dhcp"

; Pick other routes you want to push to the clients:
;push "route 10.0.0.0 255.0.0.0"
;push "route 172.16.0.0 255.240.0.0"
;push "route 192.168.0.0 255.255.0.0"
;push "route 100.64.0.0 255.192.0.0"

keepalive 10 120

persist-key
persist-tun

;mute 20
;explicit-exit-notify 1

/etc/openvpn/server/radiusplugin.cnf

# The NAS identifier which is sent to the RADIUS server
# The service type which is sent to the RADIUS server
# The framed protocol which is sent to the RADIUS server
# The NAS port type which is sent to the RADIUS server
# The NAS IP address which is sent to the RADIUS server
NAS-Identifier=something-that-is-meaningful
Service-Type=2
Framed-Protocol=1
NAS-Port-Type=5
NAS-IP-Address=192.168.1.1

# Path to the OpenVPN configfile. The plugin searches there for
# client-config-dir PATH   (searches for the path)
# status FILE                      (searches for the file, version must be 1)
# client-cert-not-required (if the option is used or not)
# username-as-common-name  (if the option is used or not)
OpenVPNConfig=/etc/openvpn/server/server.conf


# Support for topology option in OpenVPN 2.1
# If you don't specify anything, option "net30" (default in OpenVPN) is used.
# You can only use one of the options at the same time.
# If you use topology option "subnet", fill in the right netmask, e.g. from OpenVPN option "--server NETWORK NETMASK"
subnet=255.255.255.0
# If you use topology option "p2p", fill in the right network, e.g. from OpenVPN option "--server NETWORK NETMASK"
# p2p=10.8.0.1


# Allows the plugin to overwrite the client config in client config file directory,
# default is true
overwriteccfiles=true

# Allows the plugin to use auth control files if OpenVPN (>= 2.1 rc8) provides them.
# default is false
useauthcontrolfile=true


# Path to a script for vendor specific attributes.
# Leave it out if you don't use an own script.
#vsascript=/etc/openvpn/server/vsascript.pl

# Path to the pipe for communication with the vsascript.
# Leave it out if you don't use an own script.
#vsanamedpipe=/tmp/vsapipe

# A radius server definition, there could be more than one.
# The priority of the server depends on the order in this file. The first one has the highest priority.
server
{
        acctport=1813
        authport=1812
        retry=0
        wait=30
        name=127.0.0.1
        sharedsecret=ChangeMe
}

/etc/openvpn/server/script-auth-user-pass-verify

The script below is used currently to make sure that the username and the user certificate name match, otherwise, deny the login request.

Unfortunately, OpenVPN runs the RADIUS plugin in a different thread, so if you're using RADIUS to do PUSH notifications to Azure MFA, DUO, Okta, etc., the user will most likely receive a PUSH notification if they have entered correct credentials, even though there may be a certificate mismatch since these two processes are running in parallel.

#!/bin/bash
DUMPFILE=/var/log/openvpn/dump.all

echo "script-auth-user-pass-verify" >> $DUMPFILE
date >> $DUMPFILE
echo "==========" >> $DUMPFILE
for arg in "$@"; do
        echo "ARG: $arg" >> $DUMPFILE
done
echo "==========" >> $DUMPFILE
env >> $DUMPFILE
echo "==========" >> $DUMPFILE
echo "Pulled from environment:" >> $DUMPFILE
echo "common_name: $common_name" >> $DUMPFILE
echo "username:    $username" >> $DUMPFILE
if [ "$common_name" = "$username" ]; then
        echo "Names match!!! Allowing connection!"
        echo "==========" >> $DUMPFILE
        echo "" >> $DUMPFILE
        echo "" >> $DUMPFILE
        echo "" >> $DUMPFILE
else
        echo "$untrusted_ip:$untrusted_port script-auth-user-pass-verify: common_name and username do not match... denying connection!!!"
        echo "common_name and username do not match... denying connection!!!" >> $DUMPFILE
        echo "==========" >> $DUMPFILE
        echo "" >> $DUMPFILE
        echo "" >> $DUMPFILE
        echo "" >> $DUMPFILE
        exit 1
fi

/etc/openvpn/server/script-client-connect

This script doesn't do anything currently but log available environment variables to the dump.all file. It can be used to push additional configuration details to the OpenVPN server for the user that is logging in.

#!/bin/bash
DUMPFILE=/var/log/openvpn/dump.all

echo "script-client-connect" >> $DUMPFILE
date >> $DUMPFILE
echo "==========" >> $DUMPFILE
for arg in "$@"; do
        echo "ARG: $arg" >> $DUMPFILE
done
echo "==========" >> $DUMPFILE
env >> $DUMPFILE
echo "==========" >> $DUMPFILE
echo "" >> $DUMPFILE
echo "" >> $DUMPFILE
echo "" >> $DUMPFILE

OpenVPN Client Configuration

setenv FRIENDLY_NAME "Meaningful name"
setenv USERNAME "actual.username@some.domain.com"
setenv ALLOW_PASSWORD_SAVE 0

remote vpn.your.domain.here.com 1194

client
proto udp
dev tun1

connect-retry 300
connect-retry-max 0
ping 20

remote-cert-tls server
auth-user-pass
auth-retry interact
cipher AES-256-GCM
auth SHA256

nobind
persist-key
persist-tun
reneg-sec 0

<ca>
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
</ca>

key-direction 1

<tls-auth>
-----BEGIN OpenVPN Static key V1-----
-----END OpenVPN Static key V1-----
</tls-auth>

<key>
-----BEGIN PRIVATE KEY-----
-----END PRIVATE KEY-----
</key>

<cert>
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
</cert>

 

 

 

Project: Teensy Climate

Original content is located here.

The primary purpose of this project was to give me a reason to learn how to develop solutions using the Atmel AVR 8-bit microcontrollers.

The secondary purpose of this project is to develop a climate control system for the home.

Current Project Status

4 Dec 2014:

Really haven't done anything with this project since the last update primarily because I wasn't finding a decent relay module for the fan attic fan controls easily enough. Earlier this week someone showed me the SainSmart relay modules. They just came in today. Hopefully I'll get to play with soon and get them integrated into the project. The ultimate goal of this project was to have a device automatically control the attic fan during the sprint, summer and autumn months. With these boards I now see a light at the end of the tunnel.


30 July 2011:

The code has been cleaned up a little bit (you should have seen versions 0.1-0.4!!!).

While I am using the uthash library, I know I'm not using it properly, but it works. While I wanted to implement a hash table, it's pretty much just creating a linked list for me and I'm iterating the list in various places. I've tried implementing a standard linked list using pointers (in version 0.6) but it's not working properly. The sensor objects are being created with the proper hardware addresses in each location, but the temperature readings are not being stored right, which is just strange. So for now I'm continuing to eat up some extra memory while I use the uthash library.

I'm trying to determine the best relay setup to use to control the attic fan. I guess I just need to get over it and by a $15-20 120-240V relay and be done with it.

To Date min's and max's

Development Photos

Breadboarded project
  1. Teensy 2.0 Development Board
  2. LCD Contrast Potentiometer (10k)
  3. DS18B20 Programmable Resolution 1-Wire Digital Thermometer: two on board and one in attic
  4. CAT3 extending 1-Wire bus to sensor in attic
  5. HD44780 compatible LCD display
Serial console extended stats
Serial console CLI options
Graphed data from 8 Aug 2011 (dark green is attic temperature, others are inside temperature)

The CLI was updated to export data using a CSV format (millis,now(),each sensors last conversion...). A Python script was written to read the text from the serial interface and write it to a file. LiveGraph was used to read the file in real time and display the graphed data. The graph above begins at about 11:30 PM 7 Aug 2011 and ends about 11:30 PM 8 Aug 2011. One sample is output to the CLI every second. After roughly a 24 hour period the CSV file was 2.69MB in size.

Version

The current version is 0.5c.

Features

As of 0.5c (1 Aug 2011):

As of 0.5b (30 July 2011):

To Do

Source Code

/*
Starting point:
 http://tushev.org/articles/electronics/42-how-it-works-ds18b20-and-arduino
 20150822 Forked from temp_05b_hash_realtime_metrof

 -= TODO =-

 A/C Filter Change Reminder

 -= EEPROM STORAGE MAP =-
 1k bytes EEPROM available
 Page size is 8 bytes
 128 Pages

 Reserve the first page for empty (due to slot 0 possiblity of being corrupted)

 Reserve pages 2-9 for configuration data

 TIMEZONE_OFFSET_HOURS - char
 DST - daylight savings time - byte
 NUM_STORED_SENSORS - byte
 AIRFILTER REPLACEMENT DATE
 AIRFILTER LIFESPAN

 Pages 10 and up are reserved for stored sensors

 STORED SENSORS:
 ADDR - byte[8] (1 page)
 NAME - char[10] (1 page plus 2 bytes)
 CALIBRATION_OFFSET - float (2 bytes)

 */

/*
 * Teensy 2.0 pinout details for this project
 * ==========================================
 * 0  - 
 * 1  - 
 * 2  - 
 * 3  - 
 * 4  - 
 * 5  - * i2c SCL
 * 6  - * i2c SDA
 * 7  - 
 * 8  - 
 * 9  - 
 * 10 - * OneWire bus
 * 11 - * LED_PIN Used for onboard signalling LED
 * 12 - * LCD Display
 * 13 - * LCD Display
 * 14 - * LCD Display
 * 15 - * LCD Display
 * 16 - * LCD Display
 * 17 - * LCD Display
 * 18 - 
 * 19 - 
 * 20 - 
 * 21 - A0 - Connected to Adafruit GA1A12S202 Log-scale Analog Light Sensor (response from 3 to 55,000 lux)
 * 
 * i2c Device Addresses
 * ====================
 * 0x29 - Adafruit TSL2591 High Dynamic Range Digital Light Sensor
 * 
 */

// Uncomment the line below to enable DEBUG information. This will cause the compiled code to increase.
//#define DEBUG 0

#define SKETCHVERSION 7

#define ACTIVITY_CHAR_INTERVAL 250
#define BLINK_INTERVAL 250
#define CONSOLE_UPDATE_INTERVAL 1000
#define DEFAULT_MIN_TEMP 999
#define DEFAULT_MAX_TEMP -99
#define DEFAULT_TIME_ZONE_OFFSET -5  // CST during DST
#define DS18B20_ID 0x28
#define DS18B20_CONVERSION_WAIT_TIME 750
#define HELP_PAUSE_METRO_INTERVAL 10000
#define LCD_CLEAR_INTERVAL 10000
#define LCD_UPDATE_INTERVAL 1000
#define LCD_STRING_BUFFER_LENGTH 21
#define LED_PIN 11 // physical pin 11

#define ANALOG_LUX_SENSOR_PIN A0 // physical pin 21
#define LIGHTANALOGSENSOR 250
#define LIGHTDIGITALSENSOR 1000

#include <avr/pgmspace.h>
#include <MemoryFree.h>
#include <LiquidCrystal.h>
#include <Metro.h>
#include <OneWire.h>
#include <String.h>
#include "C:\ArduinoProjects\lib\Streaming.h"
#include <Time.h>
#include "C:\ArduinoProjects\lib\uthash-master\uthash.h"

// 20150822 MSHARP - Adding TSL2591 light sensor support
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include "Adafruit_TSL2591.h"

enum console_mode_t {
  standard,
  extended,
  streaming
};

console_mode_t consoleMode = standard;

// ===== ===== ===== ===== ===== ===== ===== ===== ===== ===== ===== =====
// Using PROGMEM ROM to store strings
// Use printPROGMEMString() to print these strings to the serial port
// ===== ===== ===== ===== ===== ===== ===== ===== ===== ===== ===== =====

const char version_line_1[] PROGMEM = "Sketch version: ";

const char message_settingup [] PROGMEM = "Setting up...";

const char message_timesync_waiting [] PROGMEM = "Waiting for time data in @time_t format...";
const char message_timesync_updated [] PROGMEM = "Sync message received and time updated.";
const char message_timesync_invalid [] PROGMEM = "Invalid sync message received.";

const char message_bootloader_jump_1 [] PROGMEM = "Jumping to bootloader in ";
const char message_bootloader_jump_2 [] PROGMEM = "Make sure you close your serial console!!!";
const char message_bootloader_jump_jumping [] PROGMEM = "Jumping!";

const char message_pressanykeytoabort [] PROGMEM = "PRESS ANY KEY TO ABORT!!!";
const char message_aborted [] PROGMEM = "Aborted!";

// ===== ===== ===== ===== ===== ===== ===== ===== ===== ===== ===== =====

#define CLI_STRING_BUFFER_LENGTH 50
#define NUM_CLI_LINES 13

const char cli_line_1[] PROGMEM  = "CLI Options:";
const char cli_line_2[] PROGMEM  = "B:     jump to bootloader";
const char cli_line_3[] PROGMEM  = "c:     cycle console mode";
const char cli_line_4[] PROGMEM  = "C:     clear sensors stats";
const char cli_line_5[] PROGMEM  = "d/D:   toggle lcd display contents";
const char cli_line_6[] PROGMEM  = "f/F:   show current free memory";
const char cli_line_7[] PROGMEM  = "G:     reset global sensor stats";
const char cli_line_8[] PROGMEM  = "h:     display this help";
const char cli_line_9[] PROGMEM  = "l:     query analog light sensor and display result";
const char cli_line_10[] PROGMEM = "L:     query digital light sensor and display result";
const char cli_line_11[] PROGMEM = "s/S:   set console mode to streaming";
const char cli_line_12[] PROGMEM = "T:     time sync via console";
const char cli_line_13[] PROGMEM = "v/V:   show version";

const char* const cli_string_table[] PROGMEM =
{
  cli_line_1,
  cli_line_2,
  cli_line_3,
  cli_line_4,
  cli_line_5,
  cli_line_6,
  cli_line_7,
  cli_line_8,
  cli_line_9,
  cli_line_10,
  cli_line_11,
  cli_line_12,
  cli_line_13
};

// ===== ===== ===== ===== ===== ===== ===== ===== ===== ===== ===== =====

// These are sensors that I know I have. Eventually we'll store this in EEPROM programatically
byte tempSensorAttic[8] = {
  0x28, 0xdb, 0x32, 0x5b, 0x03, 0x00, 0x00, 0x8d
};
byte tempSensorInside1[8] = {
  0x28, 0xb7, 0x4b, 0x5b, 0x03, 0x00, 0x00, 0xad
};
byte tempSensorInside2[8] = {
  0x28, 0x39, 0x44, 0x5b, 0x03, 0x00, 0x00, 0x3b
};

boolean consolePaused = false;
boolean lcdDisplayAddresses = false;
boolean showExtendedStats = false;

unsigned int activityCharIndex = 0;
unsigned int ledStatus = 0;

unsigned long loopIteration = 0;
unsigned long loopStartMillis;
unsigned long lastLoopDuration = 0;

// LCD Display using 6 pins (12-17)
LiquidCrystal lcd(16, 17, 12, 13, 14, 15);

// Adafruit TSL2591 High Dynamic Range Digital Light Sensor
boolean tslFound = false;
Adafruit_TSL2591 tsl = Adafruit_TSL2591(2591);

// Global variables for templerature sensors
const int luxAnalogNumReadings = 10;
float luxAnalogReadings[luxAnalogNumReadings];
float luxAnalogRaw = 0.0;
int luxAnalogRawIndex = 0;
float luxAnalogRawTotal = 0.0;
float luxAnalogRawAverage = 0.0;
float luxAnalogLog = 0.0;
float luxAnalogLogSmoothed = 0.0;

uint32_t luxDigitalFullLuminosity;
uint16_t luxDigitalIR, luxDigitalFull, luxDigitalCalculated;

// Manually update this number when you add a Metro below
#define NUM_METROS 7

Metro activityCharMetro(ACTIVITY_CHAR_INTERVAL, false);
Metro consoleUpdateMetro(CONSOLE_UPDATE_INTERVAL);
Metro helpPauseMetro(HELP_PAUSE_METRO_INTERVAL, false);
Metro ledMetro(BLINK_INTERVAL, false);
Metro lcdUpdateMetro(LCD_UPDATE_INTERVAL);
Metro lcdClearMetro(LCD_CLEAR_INTERVAL);  // counter to clear the lcd every 10 seconds for housekeeping
Metro owBusSearchMetro(600000, false);  //  counter to search the bus every 10 minutes for new devices
Metro lightAnalogSensor(LIGHTANALOGSENSOR, false);
Metro lightDigitalSensor(LIGHTDIGITALSENSOR, false);
  
// OneWire bus on pin 10
OneWire ds(10);

float globalMin = DEFAULT_MIN_TEMP;
char * globalMinName = NULL;
time_t globalMinTimeStamp = (time_t) 0;
float globalMax = DEFAULT_MAX_TEMP;
char * globalMaxName = NULL;
time_t globalMaxTimeStamp = (time_t) 0;

struct DS18B20 {
  byte addr[8];              /* key */
  char name[10];
  boolean active;
  boolean converting;  /* true if a conversion has been requested */
  unsigned int crcerrors;
  unsigned long startConversionLI;  // loop iteration of the start conversion
  unsigned long liLastConversion;    // number of loop iterations for the last conversion
  Metro conversionTimer;
  float lastTemp;
  time_t lastTimeStamp;
  float minTemp;
  time_t minTimeStamp;
  float maxTemp;
  time_t maxTimeStamp;
  UT_hash_handle hh;         /* makes this structure hashable */
};

struct DS18B20 *tempSensors = NULL;

boolean compareByteArray(byte a1[], byte a2[]) {
  int arraySize = sizeof(a1) / sizeof(byte);
  if (sizeof(a1) != sizeof(a2)) {
    return false;
  }
  for (int i = 0; i < arraySize; i++) {
    if (a1[i] != a2[i]) {
      return false;
    }
  }
  return true;
}

void add_sensor(byte addr[]) {
  struct DS18B20 *s;

  s = (DS18B20 *) malloc(sizeof(struct DS18B20));
  for (int i = 0; i < 8; i++) {
    s->addr[i] = addr[i];
  }
  s->active = false;
  s->converting = false;
  s->crcerrors = 0;
  s->conversionTimer = Metro(DS18B20_CONVERSION_WAIT_TIME, false);
  s->liLastConversion = 0;
  s->lastTemp = 0.0;
  s->lastTimeStamp = now();
  s->minTemp = DEFAULT_MIN_TEMP;
  s->minTimeStamp = s->lastTimeStamp;
  s->maxTemp = DEFAULT_MAX_TEMP;
  s->maxTimeStamp = s->lastTimeStamp;
  if (compareByteArray(s->addr, tempSensorAttic)) {
    strcpy(s->name, "Attic");
  }
  else if (compareByteArray(s->addr, tempSensorInside1)) {
    strcpy(s->name, "Inside1");
  }
  else if (compareByteArray(s->addr, tempSensorInside2)) {
    strcpy(s->name, "Inside2");
  }
  else {
    strcpy(s->name, "Unknown");
  }
  HASH_ADD(hh, tempSensors, addr, sizeof(byte) * 8, s);
}

struct DS18B20 *find_sensor(byte addr[]) {
  struct DS18B20 *s;

#ifdef DEBUG
  Serial.print("find_sensor(");
  Serial.print(OneWireaddrtostring(addr, false));
  Serial.println(")");
#endif

  for (s = tempSensors; s != NULL; s = (DS18B20 *) s->hh.next) {
#ifdef DEBUG
    Serial.println("Comparing:");
    Serial.print(OneWireaddrtostring(s->addr, false));
    Serial.print(" to ");
    Serial.println(OneWireaddrtostring(addr, false));
#endif

    if (compareByteArray(addr, s->addr)) {
#ifdef DEBUG
      Serial.println("  match found... sensor already detected");
#endif
      return s;
    }
  }

#ifdef DEBUG
  Serial.println("no match found... returning NULL");
#endif
  return NULL;
}

void update_console() {
  struct DS18B20 *s;

  // Do a digital lux reading before we attempt displaying anything... this will make the display update more pleasing
  doLuxReadingDigital();

  if ((consoleMode == standard) || (consoleMode == extended)) {
    // Now lets start putting stuff on the console
    serialPrintDateTime(now());
    Serial << " (Uptime: " << millis() / 1000 << " s) (li: " << loopIteration << ") (lld: " << lastLoopDuration << " ms)" << endl;
    Serial << "Global Min: " << globalMin << " @ ";
    serialPrintDateTime(globalMinTimeStamp);
    Serial << " (" << globalMinName << ")" << endl;
    Serial << "Global Max: " << globalMax << " @ ";
    serialPrintDateTime(globalMaxTimeStamp);
    Serial << " (" << globalMaxName << ")" << endl << endl;
    Serial.println("Current list of sensors:");

    for (s = tempSensors; s != NULL; s = (DS18B20 *) s->hh.next) {
      Serial.print(OneWireaddrtostring(s->addr, false));
      if (s->active) {
        if (consoleMode == standard) {
          // show standard information
          Serial.print(" ");
          Serial.print(s->lastTemp);
          Serial.print("/");
          Serial.print(s->minTemp);
          Serial.print("/");
          Serial.print(s->maxTemp);
          Serial.print(" F, Location: ");
          Serial.println(s->name);
        }
        else {
          // show extended stats
          Serial << " Location: " << s->name << " (crcerrors: " << s->crcerrors << " ) (lis: " << s->liLastConversion << ")" << endl;
          Serial << "    Last: " << s->lastTemp << " @ ";
          serialPrintDateTime(s->lastTimeStamp);
          Serial << endl;
          Serial << "    Min: " << s->minTemp << " @ ";
          serialPrintDateTime(s->minTimeStamp);
          Serial << endl;
          Serial << "    Max: " << s->maxTemp << " @ ";
          serialPrintDateTime(s->maxTimeStamp);
          Serial << endl;
        }
      }
      else {
        Serial.println(" is pending first read.");
      }
    }
    // Display light sensor data now
    Serial.println();
    displayAnalogLightSensorData();
    displayDigitalLightSensorData();
  }
  else {
    // streaming
    //        Serial << "@" << endl;
    //     serialPrintDateTime(now());
    Serial << millis() << "," << now();
    Serial.print(",");
    /*     Serial << "," << millis() / 1000 << endl;
         Serial << globalMin << ",";
         serialPrintDateTime(globalMinTimeStamp);
         Serial << "," << globalMinName << endl;
         Serial << globalMax << ",";
         serialPrintDateTime(globalMaxTimeStamp);
         Serial << "," << globalMaxName << endl;
         */
    for (s = tempSensors; s != NULL; s = (DS18B20 *) s->hh.next) {
      //      Serial.print(OneWireaddrtostring(s->addr, false));
      //      Serial.print(",");
      Serial.print(s->lastTemp);
      if (s->hh.next != NULL) {
        Serial.print(",");
      }
      /*      serialPrintDateTime(s->lastTimeStamp);
       Serial.print(",");
       Serial.print(s->minTemp);
       Serial.print(",");
       serialPrintDateTime(s->minTimeStamp);
       Serial.print(",");
       Serial.print(s->maxTemp);
       Serial.print(",");
       serialPrintDateTime(s->maxTimeStamp);
       Serial.print(",");
       Serial.println(s->name); */
    }

    Serial.print(",LA,");
    Serial.print(luxAnalogReadings[luxAnalogRawIndex]);
    Serial.print(",");
    Serial.print(luxAnalogRawAverage);
    Serial.print(",");
    Serial.print(pow(10, luxAnalogLog));
    Serial.print(",");
    Serial.print(pow(10, luxAnalogLogSmoothed));

    if (tslFound) {
      Serial.print(",LD,");
      Serial.print("IR,"); Serial.print(luxDigitalIR);  Serial.print(",");
      Serial.print("F,"); Serial.print(luxDigitalFull); Serial.print(",");
      Serial.print("V,"); Serial.print(luxDigitalFull - luxDigitalIR); Serial.print(",");
      Serial.print("L,"); Serial.print(luxDigitalCalculated);
    }
  }
  Serial.println();
}

boolean update_sensor(byte addr[], float temp) {
  struct DS18B20 *s;

  s = find_sensor(addr);
  if (s != NULL) {
    if (!s->active) {
      s->active = true;
    }

    s->lastTemp = temp;
    s->lastTimeStamp = now();

    if (temp < s->minTemp) {
      s->minTemp = temp;
      s->minTimeStamp = now();
    }

    if (temp < globalMin) {
      globalMin = temp;
      globalMinName = s->name;
      globalMinTimeStamp = now();
    }

    if (temp > s->maxTemp) {
      s->maxTemp = temp;
      s->maxTimeStamp = now();
    }

    if (temp > globalMax) {
      globalMax = temp;
      globalMaxName = s->name;
      globalMaxTimeStamp = now();
    }

    return true;
  }
  else {
#ifdef DEBUG
    Serial.print("update_sensor() failed for addr ");
    Serial.print(OneWireaddrtostring(addr, false));
    Serial.print(" ");
    Serial.print(temp);
    Serial.println(" F");
#endif
    return false;
  }
}

int count_sensors() {
  struct DS18B20 *s;
  int count = 0;
  for (s = tempSensors; s != NULL; s = (DS18B20 *) s->hh.next, count++) {
  }
  return count;
}

void clear_sensor_stats() {
  struct DS18B20 *s;

  for (s = tempSensors; s != NULL; s = (DS18B20 *) s->hh.next) {
    s->minTemp = DEFAULT_MIN_TEMP;
    s->minTimeStamp = (time_t) 0;
    s->maxTemp = DEFAULT_MAX_TEMP;
    s->maxTimeStamp = (time_t) 0;
  }
}

void clear_global_sensor_stats() {
  globalMin = DEFAULT_MIN_TEMP;
  globalMinName = NULL;
  globalMinTimeStamp = (time_t) 0;
  globalMax = DEFAULT_MAX_TEMP;
  globalMaxName = NULL;
  globalMaxTimeStamp = (time_t) 0;
}

void doLuxReadingAnalog() {
  float rawRange = 1024; // 3.3v
  float logRange = 5.0;  // 3.3 = 10^5 lux

  luxAnalogRawTotal = luxAnalogRawTotal - luxAnalogReadings[luxAnalogRawIndex];
  luxAnalogReadings[luxAnalogRawIndex] = analogRead(ANALOG_LUX_SENSOR_PIN);
  luxAnalogRawTotal = luxAnalogRawTotal + luxAnalogReadings[luxAnalogRawIndex];
  luxAnalogRawAverage = luxAnalogRawTotal / luxAnalogNumReadings;
  luxAnalogLog = luxAnalogRawAverage * logRange / rawRange;
  luxAnalogLogSmoothed = luxAnalogRawAverage * logRange / rawRange;
  
  luxAnalogRawIndex = luxAnalogRawIndex + 1;
  if (luxAnalogRawIndex >= luxAnalogNumReadings){
    luxAnalogRawIndex = 0;
  }
  
}

void displayAnalogLightSensorData() {
  Serial.print("Light [analog] : ");
  Serial.print("Raw: ");
  Serial.print(luxAnalogReadings[luxAnalogRawIndex]);
  Serial.print("  Smoothed: ");
  Serial.print(luxAnalogRawAverage);
  Serial.print("  Log: ");
  Serial.print(pow(10, luxAnalogLog));
  Serial.print("  Smoothed: ");
  Serial.println(pow(10, luxAnalogLogSmoothed));
}

// Updates global variables to be used in displayDigitalLightSensorDataData()
void doLuxReadingDigital() {
  if (!tslFound) {
    Serial.println("No TSL2591 device has been found.");
    return;
  }

  // You can change the gain on the fly, to adapt to brighter/dimmer light situations
  //tsl.setGain(TSL2591_GAIN_LOW);    // 1x gain (bright light)
  tsl.setGain(TSL2591_GAIN_MED);      // 25x gain
  //tsl.setGain(TSL2591_GAIN_HIGH);   // 428x gain

  // Changing the integration time gives you a longer time over which to sense light
  // longer timelines are slower, but are good in very low light situtations!
  tsl.setTiming(TSL2591_INTEGRATIONTIME_100MS);  // shortest integration time (bright light)
  //tsl.setTiming(TSL2591_INTEGRATIONTIME_200MS);
  //tsl.setTiming(TSL2591_INTEGRATIONTIME_300MS);
  //tsl.setTiming(TSL2591_INTEGRATIONTIME_400MS);
  //tsl.setTiming(TSL2591_INTEGRATIONTIME_500MS);
  //tsl.setTiming(TSL2591_INTEGRATIONTIME_600MS);  // longest integration time (dim light)

  luxDigitalFullLuminosity = tsl.getFullLuminosity();
  luxDigitalIR = luxDigitalFullLuminosity >> 16;
  luxDigitalFull = luxDigitalFullLuminosity & 0xFFFF;
  luxDigitalCalculated = tsl.calculateLux(luxDigitalFull, luxDigitalIR);
}

void displayDigitalLightSensorData() {
  if (!tslFound) {
    Serial.println("No TSL2591 device has been found.");
    return;
  }
  
  Serial.print("Light [digital]: ");
  Serial.print("IR: "); Serial.print(luxDigitalIR);  Serial.print("  ");
  Serial.print("Full: "); Serial.print(luxDigitalFull); Serial.print("  ");
  Serial.print("Visible: "); Serial.print(luxDigitalFull - luxDigitalIR); Serial.print("  ");
  Serial.print("Lux: "); Serial.print(luxDigitalCalculated);
  Serial.println();
}

boolean findDS18B20Devices(OneWire & ow) {
  byte addr[8];
  unsigned int deviceCount = 0;

#ifdef DEBUG
  Serial.println("Searching bus...");
#endif

  //find a device
  while (ow.search(addr)) {
    if (OneWire::crc8( addr, 7) != addr[7]) {
      Serial.println("Bad crc!!!");
      continue;
    }

    if (addr[0] != DS18B20_ID) {
#ifdef DEBUG
      Serial.print("Unknown device: ");
#endif
      Serial.println(OneWireaddrtostring(addr, false));
      continue;
    }

#ifdef DEBUG
    Serial.print("Found a device:");
    Serial.println(OneWireaddrtostring(addr, false));
#endif

    deviceCount++;
    if (find_sensor(addr) == NULL) {
#ifdef DEBUG
      Serial.print("Adding new sensor to list: ");
#endif
      add_sensor(addr);
    }
#ifdef DEBUG
    else {
      Serial.print("We already know about this sensor: ");
    }
#endif
    Serial.println(OneWireaddrtostring(addr, false));
    Serial.println();
  }

#ifdef DEBUG
  Serial.println("No more devices found... resetting search.");
  Serial.println();
#endif
  ow.reset_search();
}

boolean requestTemperatureConversion(OneWire ow, DS18B20 *sensor) {
  ow.reset();
  ow.select(sensor->addr);
  ow.write(0x44, 1);

  return true;
}

float retrieveTemperature(OneWire ow, DS18B20 *sensor) {
  byte data[12];
  float temp;

  ow.reset();
  ow.select(sensor->addr);
  ow.write(0xBE);
  for (int i = 0; i < 9; i++) {
    data[i] = ow.read();
  }

  temp = ( (data[1] << 8) + data[0] ) * 0.0625; // tempc
  temp = (temp * 1.8) + 32;  // tempf

  if (OneWire::crc8( data, 8) != data[8]) {
    temp = -9999;
  }

  return temp;
}

void toggleLED() {
  switch (ledStatus) {
    case 1:
      digitalWrite(LED_PIN, LOW);
      ledStatus = 0;
      break;
    default:
      digitalWrite(LED_PIN, HIGH);
      ledStatus = 1;
      break;
  }
}

void showActivityChar() {
  lcd.setCursor(18, 0);
  switch (activityCharIndex) {
    case 1:
      activityCharIndex--;
      lcd.print(count_sensors());
      lcd.print(" ");
      break;
    default:
      activityCharIndex++;
      lcd.print(count_sensors());
      lcd.print("*");
      break;
  }
}

String OneWireaddrtostring(byte addr[], boolean lcd) {
  String toReturn;

  for ( int i = 0; i < 8; i++) {
    if (addr[i] < 16) {
      toReturn += "0";
    }
    toReturn += String(addr[i], HEX);
    if (i < 7) {
      if (!lcd) {
        // don't print semicolons on the lcd
        // we don't have enough room
        toReturn += ":";
      }
    }
  }

  return toReturn;
}

void update_lcd() {
  struct DS18B20 *s;
  unsigned int count;
  unsigned int maxCount = 2;

  s = tempSensors;
  if (lcdDisplayAddresses) {
    maxCount = 3;
  }

  if (lcdClearMetro.check() == 1) {
    lcd.clear();
    lcdClearMetro.reset();
  }

  if (lcdDisplayAddresses) {
    lcd.setCursor(0, 0);
    lcd.print("Uptime: ");
    lcd.print(millis() / 1000);
    lcd.print("s ");
  }

  for (count = 0; count < maxCount; count++, s = (DS18B20 *) s->hh.next) {
    if (s == NULL) {
      break;
    }

    if (!lcdDisplayAddresses) {
      if (!s->active) {
        continue;
      }
      lcd.setCursor(count * 10, 0);
      lcd.print(s->name);
      lcd.setCursor(count * 10, 1);
      lcd.print(s->lastTemp);
      lcd.print((char)223);
      lcd.print("F");
      lcd.setCursor(count * 10, 2);
      lcd.print(s->minTemp);
      lcd.print((char)223);
      lcd.print("F");
      lcd.setCursor(count * 10, 3);
      lcd.print(s->maxTemp);
      lcd.print((char)223);
      lcd.print("F");
    }
    else {
      // display addresses instead of temps
      lcd.setCursor(0, count + 1);
      lcd.print("    ");
      lcd.print(OneWireaddrtostring(s->addr, true));
      lcd.setCursor(0, count + 1);
      lcd.print(s->name);
      lcd.print(":");
    }
  }
}

void serialPrintDateTime(time_t timeStamp) {
  time_t timeStampAdjusted = timeStamp + (DEFAULT_TIME_ZONE_OFFSET * 60 * 60);
  Serial << month(timeStampAdjusted) << "/" << day(timeStampAdjusted) << "/" << year(timeStampAdjusted) << " " << hour(timeStampAdjusted) << ":";
  if (minute(timeStampAdjusted) < 10) Serial << "0";
  Serial << minute(timeStampAdjusted) << ":";
  if (second(timeStampAdjusted) < 10) Serial << "0";
  Serial << second(timeStampAdjusted);
}

void processTimeSyncMessage() {
  int count = 0;
  char buf[11];
  boolean status = false;  // did we get good data
  printPROGMEMString(message_timesync_waiting); // "Waiting for time data in @time_t format..."
  Serial.println();
  Serial.flush();
  while (count < 11) {
    if (Serial.available()) {  // receive all 11 bytes into "buf"
      buf[count++] = Serial.read();
    }
  }
  if (buf[0] == '@') {
    time_t pctime = 0;
    for (int i = 1; i < 11; i++) {
      char c = buf[i];
      if (c >= '0' && c <= '9') {
        pctime = (10 * pctime) + (c - '0') ; // convert digits to a number
      }
    }
    pctime += 10;
    setTime(pctime);   // Sync clock to the time received
    status = true;
  }
  if (status) {
    // "Sync message received and time updated."
    printPROGMEMString(message_timesync_updated);
    Serial.println();
  }
  else {
    // "Invalid sync message received."
    printPROGMEMString(message_timesync_invalid);
    Serial.println();
  }
}

void jumpToBootloader() {
  unsigned int counter = 10;
  printPROGMEMString(message_bootloader_jump_1); // "Jumping to bootloader in "
  Serial << counter << " seconds...";
  Serial.println();
  printPROGMEMString(message_bootloader_jump_2); // "Make sure you close your serial console!!!"
  Serial.println(); Serial.println();
  printPROGMEMString(message_pressanykeytoabort); // "PRESS ANY KEY TO ABORT!!!"
  Serial.println(); Serial.println();
  Serial.flush();
  lcd.clear();
  while (counter > 0) {
    lcd.setCursor(0, 0);
    lcd.print(counter);
    Serial << counter << " ";
    if (Serial.available()) {
      Serial.flush();
      Serial.println(); Serial.println();
      printPROGMEMString(message_aborted); // "Aborted!"
      Serial.println(); Serial.println(); Serial.println();
      return;
    }
    delay(1000);
    counter--;
  }
  printPROGMEMString(message_bootloader_jump_jumping); // "Jumping!"
  Serial.println();
  cli();
  // disable watchdog, if enabled
  // disable all peripherals
  UDCON = 1;
  USBCON = (1 << FRZCLK); // disable USB
  UCSR1B = 0;
  delay(5);
#if defined(__AVR_AT90USB162__)                // Teensy 1.0
  EIMSK = 0; PCICR = 0; SPCR = 0; ACSR = 0; EECR = 0;
  TIMSK0 = 0; TIMSK1 = 0; UCSR1B = 0;
  DDRB = 0; DDRC = 0; DDRD = 0;
  PORTB = 0; PORTC = 0; PORTD = 0;
  asm volatile("jmp 0x3E00");
#elif defined(__AVR_ATmega32U4__)              // Teensy 2.0
  EIMSK = 0; PCICR = 0; SPCR = 0; ACSR = 0; EECR = 0; ADCSRA = 0;
  TIMSK0 = 0; TIMSK1 = 0; TIMSK3 = 0; TIMSK4 = 0; UCSR1B = 0; TWCR = 0;
  DDRB = 0; DDRC = 0; DDRD = 0; DDRE = 0; DDRF = 0; TWCR = 0;
  PORTB = 0; PORTC = 0; PORTD = 0; PORTE = 0; PORTF = 0;
  asm volatile("jmp 0x7E00");
#elif defined(__AVR_AT90USB646__)              // Teensy++ 1.0
  EIMSK = 0; PCICR = 0; SPCR = 0; ACSR = 0; EECR = 0; ADCSRA = 0;
  TIMSK0 = 0; TIMSK1 = 0; TIMSK2 = 0; TIMSK3 = 0; UCSR1B = 0; TWCR = 0;
  DDRA = 0; DDRB = 0; DDRC = 0; DDRD = 0; DDRE = 0; DDRF = 0;
  PORTA = 0; PORTB = 0; PORTC = 0; PORTD = 0; PORTE = 0; PORTF = 0;
  asm volatile("jmp 0xFC00");
#elif defined(__AVR_AT90USB1286__)             // Teensy++ 2.0
  EIMSK = 0; PCICR = 0; SPCR = 0; ACSR = 0; EECR = 0; ADCSRA = 0;
  TIMSK0 = 0; TIMSK1 = 0; TIMSK2 = 0; TIMSK3 = 0; UCSR1B = 0; TWCR = 0;
  DDRA = 0; DDRB = 0; DDRC = 0; DDRD = 0; DDRE = 0; DDRF = 0;
  PORTA = 0; PORTB = 0; PORTC = 0; PORTD = 0; PORTE = 0; PORTF = 0;
  asm volatile("jmp 0x1FC00");
#endif
}

void printCLIOptions() {
//  char buf[CLI_STRING_BUFFER_LENGTH];

  for (int i = 0; i < NUM_CLI_LINES; i++) {
//    strcpy_P(buf, (char*)pgm_read_word(&(cli_string_table[i])));
//    Serial.println(buf);
    printPROGMEMString((char*) pgm_read_word(&(cli_string_table[i])));
    Serial.println();
  }

  Serial.println();
}

// 20150822 MSHARP - Added function
void printPROGMEMString(const char* PMSTRING) {
  int i;
  int len = strlen_P(PMSTRING);
  char nextCharacter;
  for (i = 0; i < len; i++) {
    nextCharacter = pgm_read_byte_near(PMSTRING + i);
    Serial.print(nextCharacter);
  }

}

void setup() {
  // Initialize the display and tell the world we're starting to work
  lcd.begin(20, 4);
  lcd.setCursor(0, 0);
  lcd.print("Setting up...");

  pinMode(LED_PIN, OUTPUT);
  ledMetro.reset();
  Serial.begin(9600);

  // displaying activity char so we know we've started the initial 1-wire search
  showActivityChar();
  
  // Give human 10 seconds to connect via serial to watch for debug information
#ifdef DEBUG
  for (int i = 0; i < 10; i++) {
    Serial << i << " ";
    delay(1000);
  }
  Serial << endl;
#endif

  findDS18B20Devices(ds);

  // Setup Adafruit i2c TSL sensor
  if (tsl.begin()) {
    Serial.println("Found a TSL2591 sensor!!!");
    tslFound = true;
  }

  // Give human 10 seconds to view debug information from device scan
#ifdef DEBUG
  for (int i = 0; i < 10; i++) {
    Serial << i << " ";
    delay(1000);
  }
  Serial << endl;
#endif

  // Initialize analog lux reading smoothing array
  for (int thisReading = 0; thisReading < luxAnalogNumReadings; thisReading++) {
    luxAnalogReadings[thisReading] = 0.0;
  }
} // end setup

void loop() {
  loopIteration++;
  loopStartMillis = millis();

  float tmpTemp = 0;
  struct DS18B20 *s;

  if (activityCharMetro.check() == 1) {
    showActivityChar();
    activityCharMetro.reset();
  }

  // manage the sensors
  for (s = tempSensors; s != NULL; s = (DS18B20 *) s->hh.next) {
    if (s->converting) {
      if (s->conversionTimer.check() == 1) {
        tmpTemp = retrieveTemperature(ds, s);
        if (tmpTemp != -9999) {
          update_sensor(s->addr, tmpTemp);
        }
        else {
          s->crcerrors++;
        }
        s->liLastConversion = loopIteration - s->startConversionLI;
        s->converting = false;
        tmpTemp = 0;
      }
    }
    else {
      requestTemperatureConversion(ds, s);
      s->startConversionLI = loopIteration;
      s->conversionTimer.reset();
      s->converting = true;
    }

  } // for tempSensors

  // manage analog lux sensor
  if (lightAnalogSensor.check() == 1) {
    doLuxReadingAnalog();
    lightAnalogSensor.reset();
  }


  // process command line input
  if (Serial.available() > 0) {
    char c = Serial.read();
    switch (c) {
      case 'B':
        jumpToBootloader();
        break;
      case 'c':
        if (consoleMode == streaming) {
#ifdef DEBUG
          Serial.println("Setting console mode to standard.");
#endif
          consoleMode = standard;
        }
        else if (consoleMode == standard) {
#ifdef DEBUG
          Serial.println("Setting console mode to extended.");
#endif
          consoleMode = extended;
        }
        else if (consoleMode == extended) {
#ifdef DEBUG
          Serial.println("Setting console mode to streaming.");
#endif
          consoleMode = streaming;
        }
        break;
      case 'C':
        clear_sensor_stats();
        break;
      case 'd':
      case 'D':
        if (lcdDisplayAddresses) {
#ifdef DEBUG
          Serial.println("Switching LCD to display sensor values");
#endif
          lcdDisplayAddresses = false;
        }
        else {
#ifdef DEBUG
          Serial.println("Switching LCD to display sensor addresses");
#endif
          lcdDisplayAddresses = true;
        }
        Serial.println();
        Serial.flush();
        lcd.clear();
        break;
      case 'f':
      case 'F':
        Serial << "Free memory: " << freeMemory() << " bytes." << endl;
#ifdef DEBUG
        Serial << sizeof(DS18B20) * count_sensors() << " bytes for " << count_sensors() << " DS18B20 sensors" << endl;
        Serial << sizeof(LiquidCrystal) << " bytes for LiquidCrystal object" << endl;
        Serial << sizeof(Metro) * NUM_METROS << " bytes for " << NUM_METROS << " Metro objects" << endl;
        Serial << sizeof(OneWire) << " bytes for OneWire object" << endl;
#endif
        Serial << endl;
        break;
      case 'G':
        clear_global_sensor_stats();
        break;
      case 'l':
        displayAnalogLightSensorData();
        break;
      case 'L':
        displayDigitalLightSensorData();
        break;
      case 's':
      case 'S':
#ifdef DEBUG
        Serial.println("Setting console mode to streaming.");
#endif
        consoleMode = streaming;
        break;
      case 'T':
#ifdef DEBUG
        Serial.println("Processing time sync request...");
#endif
        processTimeSyncMessage();
#ifdef DEBUG
        Serial.println("Done!");
#endif
        Serial.println();
        break;
      case 'h':
      case 'H':
      case '?':
        printCLIOptions();
        // consoleUpdateMetro.autoreset(false); // 20150822 MSHARP commented out because autoreset is now private method
        consoleUpdateMetro.reset();
        helpPauseMetro.reset();
        consolePaused = true;
        break;
      case 'v':
      case 'V':
        printPROGMEMString(version_line_1);
        Serial.print(" ");
        Serial.println(SKETCHVERSION);
        Serial.println();
        break;
      default:
        Serial.print("Key: 0x");
        Serial.println(c, HEX);
        printCLIOptions();
    } // testing c
  }

  if (helpPauseMetro.check() == 1) {
    consolePaused = false;
    // consoleUpdateMetro.autoreset(true); // 20150822 MSHARP commented out because autoreset is now private method
    consoleUpdateMetro.reset();
  }

  if ((!consolePaused) && (consoleUpdateMetro.check() == 1)) {
    update_console();
  }

  if (lcdUpdateMetro.check() == 1) {
    update_lcd();
  }

  if (ledMetro.check() == 1) {
    toggleLED();
    ledMetro.reset();
  }

  lastLoopDuration = millis() - loopStartMillis;
} // end loop

Python

Python

Ubuntu 18.04.1, ISPConfig3, Python 3, Flask, Apache 2, and mod_wsgi

Today I spent a lot of time trying to figure out how to get a Flask application started using Python 3 on Ubuntu 18.04.1. I had previously built an application using Python 2.7, Flask, and mod_wsgi, but it had been a while and the documentation I came across just wasn't connecting the dots properly. Here's my notes after the endeavor.

For the example, the root directory of the ISPConfig3 user is going to be /var/www/clients/client1/web1/.

# install 
sudo apt install libapache2-mod-wsgi-py3 python3-pip
# disable mod_python if it's installed, or just uninstall it as mod_wsgi can't be loaded at the same time

# install python3 virtualenv globally
sudo pip3 install virtualenv

# change to the private folder where we'll create the project
cd /var/www/clients/client1/web1/private/
# create the project folder and change into it
mkdir project1 && cd project1
# create the virtual environment in the env directory, which will be created for us
virtualenv -p python3 env
# activate the virtual environment
source env/bin/activate
# install flask
pip install flask
# setup some other directories and empty files
mkdir app log
touch wsgi.py run.py config.py app/__init__.py

wsgi.py

import sys
sys.path.insert(0, "/var/www/clients/client1/web1/private/project1")

from app import app as application

run.py

# WSGI Server for Development
# Use this during development vs. apache. Can view via [url]:8001
# Run using virtualenv. 'env/bin/python run.py'
from app import app

app.run(host='0.0.0.0', port=8001, debug=True)

app/__init__.py

from flask import Flask
app = Flask(__name__)

# Configurations
#app.config.from_object('config')

@app.route('/')
def hello_world():
    return 'Hello, World!'

Apache Directives for the WSGI configuration. This is to be added into the Options - Apache Directives section of the ISPConfig3 web interface. Note a change must be made to the ISPConfig3 vhost.conf.master file in order for the IfDefine statements to work. Diff included below. This is why I had to invert my logic and use IfDefine !ProtocolHTTPS instead of IfDefine ProtocolHTTP (see note below about the Define directive).

# For this to work /usr/local/ispconfig/server/conf/vhost.conf.master must be updated to include the (Un)Define ProtocolHTTPS commands
<IfDefine !ProtocolHTTPS>
WSGIDaemonProcess client1web1 python-home=/var/www/clients/client1/web1/private/project1/env python-path=/var/www/clients/client1/web1/private/project1
</IfDefine>
WSGIProcessGroup client1web1
WSGIScriptAlias / /var/www/clients/client1/web1/private/project1/wsgi.py
WSGIPassAuthorization On

<Directory /var/www/clients/client1/web1/private/project1>
    WSGIProcessGroup client1web1
    WSGIApplicationGroup %{GLOBAL}
	WSGIScriptReloading On
    Require all granted
</Directory>

This is an example the generated Apache virtual host configuration. This is not the complete ISPConfig3 generated config file as it has all sorts of other configuration options.

<VirtualHost *:80>
ServerName test.domain.com
ServerAdmin webmaster@test.domain.com

ErrorLog /var/log/ispconfig/httpd/test.domain.com/error.log

<IfDefine !ProtocolHTTPS>
WSGIDaemonProcess client1web1 python-home=/var/www/clients/client1/web1/private/project1/env python-path=/var/www/clients/client1/web1/private/project1
</IfDefine>
WSGIProcessGroup client1web1
WSGIScriptAlias / /var/www/clients/client1/web1/private/project1/wsgi.py
WSGIPassAuthorization On

<Directory /var/www/clients/client1/web1/private/project1>
    WSGIProcessGroup client1web1
    WSGIApplicationGroup %{GLOBAL}
	WSGIScriptReloading On
    Require all granted
</Directory>

</VirtualHost>

<VirtualHost *:443>
Define ProtocolHTTPS

ServerName test.domain.com
ServerAdmin webmaster@test.domain.com

ErrorLog /var/log/ispconfig/httpd/test.domain.com/error.log

<IfModule mod_ssl.c>
	SSLEngine on
	SSLCertificateFile /var/www/clients/client1/web1/ssl/test.domain.com-le.crt
	SSLCertificateKeyFile /var/www/clients/client1/web1/ssl/test.domain.com-le.key
	SSLCertificateChainFile /var/www/clients/client1/web1/ssl/test.domain.com-le.bundle
</IfModule>

<IfDefine !ProtocolHTTPS>
WSGIDaemonProcess client1web1 python-home=/var/www/clients/client1/web1/private/project1/env python-path=/var/www/clients/client1/web1/private/project1
</IfDefine>
WSGIProcessGroup client1web1
WSGIScriptAlias / /var/www/clients/client1/web1/private/project1/wsgi.py
WSGIPassAuthorization On

<Directory /var/www/clients/client1/web1/private/project1>
    WSGIProcessGroup client1web1
    WSGIApplicationGroup %{GLOBAL}
	WSGIScriptReloading On
    Require all granted
</Directory>

UnDefine ProtocolHTTPS
</VirtualHost>

Diff of the vhost.conf.master file.

# diff -u vhost.conf.master.1 vhost.conf.master
--- vhost.conf.master.1 2018-08-23 03:50:52.539521052 -0500
+++ vhost.conf.master   2018-08-23 03:43:36.470905313 -0500
@@ -12,6 +12,9 @@

 <tmpl_loop name='vhosts'>
 <VirtualHost {tmpl_var name='ip_address'}:{tmpl_var name='port'}>
+<tmpl_if name='ssl_enabled'>
+Define ProtoHTTPS
+</tmpl_if>
 <tmpl_hook name='apache2_vhost:vhost_header'>
 <tmpl_if name='php' op='==' value='suphp'>
                DocumentRoot <tmpl_var name='web_document_root'>
@@ -524,6 +527,9 @@

 <tmpl_var name='apache_directives'>
 <tmpl_hook name='apache2_vhost:vhost_footer'>
+<tmpl_if name='ssl_enabled'>
+UnDefine ProtoHTTPS
+</tmpl_if>
 </VirtualHost>

 <tmpl_if name='apache_version' op='>=' value='2.4' format='version'>
Still to do
Source material
Note regarding the Apache Define directive

"While this directive is supported in virtual host context, the changes it makes are visible to any later configuration directives, beyond any enclosing virtual host."

In order to workaround this behavior, I used a Define statement just after the opening <VirtualHost> tag, and an UnDefine statement just before the closing </VirtualHost> tag. This effectively maintains the scope of the Define to the VirtualHost.

(Source)

Python

Flask

 

Reference code bases:

 

radsecproxy

radsecproxy Github Project Page

radsecproxy.conf man page

Configuration Example

Integration with OpenVPN radius plugin

The OpenVPN radius plugin requires that radsecproxy handle accounting requests.

Configuration Options

# Note that some block option values may reference a block by name, in which case the block name must be previously defined. Hence the order of the blocks may be significant.
# Recommended block order:
#   tls
#   rewrite
#   client
#   server
#   realm



# The rewrite actions are performed in this sequence:
# 	1. RemoveAttribute (or WhitelistAttribute)
# 	2. ModifyAttribute
# 	3. SupplementAttribute
# 	4. AddAttribute
	  
rewrite name {
	AddAttribute attribute:value
	AddVendorAttribute vendor:subattribute:value
	SupplementAttribute attribute:value
	SupplementVendorAttribute vendor:subattribute:value
	ModifyAttribute attribute:/regex/replace/
	ModifyVendorAttribute vendor:subattribute:/regex/replace/
	RemoveAttribute attribute
	RemoveVendorAttribute vendor[:subattribute]
	WhitelistMode (on|off)
	WhitelistAttribute attribute
	WhitelistVendorAttribute vendor[:subattribute]
}

tls name {
	CACertificateFile file
	CACertificatePath path
	CertificateFile file
	CertificateKeyFile file
	CertificateKeyPassword password
	PolicyOID oid
	CRLCheck (on|off)
	CacheExpiry seconds
}

client (name|fqdn|(address[/length])) {
	Host (fqdn|(address[/length])) # multiple lines allowed
	IPv4Only (on|off)
	IPv6Only (on|off)
	Type type (UDP|TCP|TLS|DTLS)
	Secret secret
	TLS tls
	CertificateNameCheck (on|off)
	matchCertificateAttribute ( CN | SubjectAltName:URI | SubjectAltName:DNS ) :/regexp/
	MatchCertificateAttribute SubjectAltName:IP:address
	DuplicateInterval seconds
	AddTTL 1-255
	TCPKeepalive (on|off)
	FticksVISCOUNTRY cc
	FticksVISINST institution
	RewriteIn rewrite
	RewriteOut rewrite
	RewriteAttribute User-Name:/regex/replace/
}

server (name|((fqdn|address)[:port])) {
	Host (fqdn|address)[:port]
	Port port
	DynamicLookupCommand command
	StatusServer (on|off|minimal|auto)
	RetryCount count
	RetryInterfval interval
	RewriteOut rewrite
	RewriteIn rewrite
	LoopPrevention (on|off)
	IPv4Only (on|off)
	IPv6Only (on|off)
	Type type
	Secret secret
	TLS tls
	CertificateNameCheck (on|off)
	matchCertificateAttribute ( CN | SubjectAltName:URI | SubjectAltName:DNS ) :/regexp/
	MatchCertificateAttribute SubjectAltName:IP:address
	AddTTL 1-255
	TCPKeepalive (on|off)
}

realm (*|realm|/regex/) {
	Server server
	AccountingServer server
	AccountingResponse (on|off)
	ReplyMessage message
}

SystemD

SystemD

SystemD and Asterisk

Asterisk

Put the following in /etc/systemd/system/asterisk.service and then run "systemctl daemon-reload && systemctl enable asterisk"

[Unit]
Description=Asterisk PBX And Telephony Daemon
Wants=network.target
After=network.target

[Service]
Type=simple
User=root
Group=root
#Environment=HOME=/var/lib/asterisk
#WorkingDirectory=/var/lib/asterisk
ExecStart=/usr/sbin/asterisk -f -C /etc/asterisk/asterisk.conf
ExecStop=/usr/sbin/asterisk -rx 'core stop now'
ExecReload=/usr/sbin/asterisk -rx 'core reload'

LimitNOFILE=65535

# safe_asterisk emulation
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

Paste below into a terminal to setup the files:

cat << EOF > /etc/systemd/system/asterisk.service[Unit]
Description=Asterisk PBX And Telephony Daemon
Wants=network.target
After=network.target

[Service]
Type=simple
User=root
Group=root
#Environment=HOME=/var/lib/asterisk
#WorkingDirectory=/var/lib/asterisk
ExecStart=/usr/sbin/asterisk -f -C /etc/asterisk/asterisk.conf
ExecStop=/usr/sbin/asterisk -rx 'core stop now'
ExecReload=/usr/sbin/asterisk -rx 'core reload'

LimitNOFILE=65535

# safe_asterisk emulation
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

EOF

Loading Dahdi

After some digging, for my purposes I found the best way to load the transcoding module is to use the systemd module loader to load the wctc4xxp transcoding module and UDEV to run dahdi_cfg once the module is loaded.

Prerequisite: Configure the /etc/dahdi/modules and /etc/dahdi/system.conf files as you normally would.

cat << EOF > /etc/dahdi/modules
# /etc/modules-load.d/dahdi is symlinked here so systemd will load it on startup
# /etc/udev/rules.d/dahdi-wctc4xxp.rules instructs udev to run dahdi_cfg after wctc4xxp module is loaded

# Digium TC400B: G729 / G723 Transcoding Engine
wctc4xxp

EOF

ln -s /etc/dahdi/modules /etc/modules-load.d/dahdi.conf

# update udev to run dahdi_cfg after loading the transcoding module
cat << EOF > /etc/udev/rules.d/dahdi-wctc4xxp.rules
KERNEL=="wctc4xxp" RUN+="/usr/sbin/dahdi_cfg"
EOF

The next time you reboot, systemd will load the module, udev will run dahdi_cfg, and then systemd will load asterisk. Granted this is only really needed if you haven't migrated away from MeetMe yet...

Single shot

Haven't had time to work on this. Here's the information I'm starting from.

SystemD

systemd-resolved

systemd-resolved is a systemd service that provides network name resolution to local applications via a D-Bus interface, the resolve NSS service, and a local DNS stub listener on 127.0.0.53.

Traditionally, /etc/resolv.conf has been used to handle name resolution. Below is an example of this file on a system that uses systemd-resolved:

# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
#     DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
# 127.0.0.53 is the systemd-resolved stub resolver.
# run "systemd-resolve --status" to see details about the actual nameservers.

nameserver 127.0.0.53
search localdomain.loc

The systemd specific configuration file is /etc/systemd/resolved.conf. An example is shown below that uses Quad9 for primary DNS resolution with fallback to Google's Public DNS. The Ubuntu man page for resolve.conf is located here.

#  This file is part of systemd.
#
#  systemd is free software; you can redistribute it and/or modify it
#  under the terms of the GNU Lesser General Public License as published by
#  the Free Software Foundation; either version 2.1 of the License, or
#  (at your option) any later version.
#
# Entries in this file show the compile time defaults.
# You can change settings by editing this file.
# Defaults can be restored by simply deleting this file.
#
# See resolved.conf(5) for details

[Resolve]
# Use Quad9 for normal resolution
DNS=9.9.9.9 149.112.112.112
# Use Google Public DNS in case of a failure of Quad9
FallbackDNS=8.8.8.8 8.8.4.4
Domains=bluecrow.net
LLMNR=no
MulticastDNS=no
#DNSSEC=no
#Cache=yes
#DNSStubListener=yes

 

 

Vim

Vim

Color Schemes

The following code with download all of the colorschemes provided by flazz and set molokai as the default.

# vim custom colors and vimrc.local changes
mkdir -p /etc/vim/custom
git clone https://github.com/flazz/vim-colorschemes.git /etc/vim/custom
cat << EOF >> /etc/vim/vimrc.local
let &runtimepath=&runtimepath . ',' . '/etc/vim/custom'
set hlsearch
set modeline
set t_Co=256
set background=dark
:colorscheme molokai
EOF