Skip to content

ODROID-C4 Hardware Platform

📟 Hardware

odroidc4 ODROID-C4 is a new generation single board computer that is more energy efficient and faster performing than ODROID-C2 which was introduced over four years ago as the world’s first affordable ARM 64bit computer.

The main CPU of the ODROID-C4 is built with a quad-core Cortex-A55 cluster with a new generation Mali-G31 GPU. The A55 cores run at 2.0Ghz without thermal throttling using the stock heat sink allowing a robust and quiet computer. The CPU multi-core performance is around 40% faster, and the system DRAM performance is 50% faster than the ODROID-C2.

Type Description
Processor Amlogic S905X3 SoC - 4 x ARM® Cortex®-A55 2.0GHz
GPU 4 x ARM® Mali-G31™ 650MHz
RAM 4GB 32bit DDR4
Flash Storage Micro-SD UHS-I SDR104 or eMMC storage option
USB3.0 Host 4 Ports
Networking Gigabit Ethernet (10/100/1000 Mbit/s)
Expansion I/O 40pin GPIOs + 7pin I2S
Power 12V/2A - Stress: 3.64W - Idle: 0.18W
Dimensions 85 x 56mm
Weight 59g

Tip

This website is hosted on the ODROID-C4 configured with a debian operating system.
The following documentation has been made with MkDocs.

GPIO

odroidc4_gpio


💿 Installing Debian

debian_banner

armbian_logo armbian is a free and open-source operating-system based on the debian linux system. An operating-system is the set of basic programs and utilities that make a computer run. Using armbian/debian on the odroid allows the access of thousands of cross-compiled packages for arm device.

A ssh server is installed by default in this release.


🛠️ Installation

Download Bullseye image

The first operation consists on downloading the armbian linux image and flashing it on eMMC.
Download the Bullseye linux image for ODROID-C4 https://www.armbian.com/odroid-c4.

Flash Bullseye image using etcher

Flash the Armbian_21.08.1_Odroidc4_bullseye_current_5.10.60.img.xz on the eMMC using etcher.

etcher


🔐 Login

debian_system

Login on armbian

Login using ssh with user: root, password: 1234 on ip: 192.168.1.20:

ssh root@192.168.1.20
Add debian user account to sudo group if necessary

During the first connection on the odroid system a new user account will be created.
If this is not the case you will have to manually create a user account:

# add new user: debian
apt-get install sudo
sudo adduser debian
sudo usermod -aG sudo debian

⚙️ System Configuration

Configure Environment

To setup debian for the ODROID-C4, follow those steps.

# fix upgrade issue
sudo sed -i 's/bullseye-desktop//g' /etc/apt/sources.list.d/armbian.list

# update system
sudo apt-get update
sudo apt-get upgrade

# update linux kernel
sudo apt-get dist-upgrade
sudo apt-get autoremove --purge
sudo apt-get clean

# disable armbian logging services
sudo systemctl disable armbian-ramlog.service
sudo systemctl disable armbian-zram-config.service
sudo sed -i 's/ENABLED=true/ENABLED=false/g' /etc/default/armbian-ramlog
sudo rm /etc/cron.d/armbian-truncate-logs
sudo rm /etc/cron.daily/armbian-ram-logging

# force cpufreq governor to schedutil
sudo sed -i 's/ENABLE=.*/ENABLE=true/g' /etc/default/cpufrequtils
sudo sed -i 's/GOVERNOR=.*/GOVERNOR=schedutil/g' /etc/default/cpufrequtils

# configure sshd daemon
sudo sed -i 's/#*MaxAuthTries [0-9].*/MaxAuthTries 3/g' /etc/ssh/sshd_config    # limit failed authentication attempts per connection to 3
sudo sed -i 's/#LoginGraceTime 2m/LoginGraceTime 20s/g' /etc/ssh/sshd_config    # allow only 20 seconds for a client to authenticate before disconnecting
sudo sed -i 's/X11Forwarding yes/X11Forwarding no/g' /etc/ssh/sshd_config       # disable X11 forwarding to reduce attack surface
sudo systemctl reload ssh

# reboot
sync
sudo reboot now
Install telnet

To access the debian board in case sshd crash, telnetd will be installed.

# install telnet daemon
sudo apt-get install telnetd

# allow all connections from same subnet
echo 'telnetd: 192.168.1.0/255.255.255.0' | sudo tee -a /etc/hosts.allow

Tip

To access the odroid device via telnet: telnet ipv4_addr.

Activate DHCP
# activate DHCP
sudo tee /etc/network/interfaces > /dev/null <<EOF
source /etc/network/interfaces.d/*

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
auto eth0
allow-hotplug eth0
iface eth0 inet dhcp
EOF
Install SSH Keys

The following procedure install ssh security keys from the client (ex: Windows) to the remote computer in order to login without the need of password.

# on desktop
ssh-keygen -t rsa

Define host/port/user parameters

REMOTE_HOST="host"  # ex: git.example.com
REMOTE_PORT="port"  # ex: 443
REMOTE_USER="user"  # ex: git
$env:REMOTE_HOST="host"  # ex: git.example.com
$env:REMOTE_PORT="port"  # ex: 443
$env:REMOTE_USER="user"  # ex: git

Append local public key to remote computer

# verify both vars are set; otherwise print an error and exit
[ -n "$REMOTE_HOST" ] && [ -n "$REMOTE_PORT" ] && [ -n "$REMOTE_USER" ] || { echo "Error: set REMOTE_HOST, REMOTE_PORT, REMOTE_USER"; return 1 2>/dev/null || exit 1; }

# append local public key to remote authorized_keys (preserves existing entries)
ssh -p "$REMOTE_PORT" "$REMOTE_USER@$REMOTE_HOST" "mkdir -p ~/.ssh && chmod 700 ~/.ssh"
cat "$HOME/.ssh/id_rsa.pub" | ssh -p "$REMOTE_PORT" "$REMOTE_USER@$REMOTE_HOST" "cat >> ~/.ssh/authorized_keys"
ssh -p "$REMOTE_PORT" "$REMOTE_USER@$REMOTE_HOST" "chmod 600 ~/.ssh/authorized_keys"
# verify both vars are set; otherwise print an error and exit
if (-not $env:REMOTE_HOST -or -not $env:REMOTE_PORT -or -not $env:REMOTE_USER) { Write-Error "Error: set REMOTE_HOST, REMOTE_PORT, REMOTE_USER"; return }

# append local public key to remote authorized_keys (preserves existing entries)
ssh -p $env:REMOTE_PORT "$($env:REMOTE_USER)@$($env:REMOTE_HOST)" "mkdir -p ~/.ssh && chmod 700 ~/.ssh"
Get-Content "$env:USERPROFILE\.ssh\id_rsa.pub" | ssh -p $env:REMOTE_PORT "$($env:REMOTE_USER)@$($env:REMOTE_HOST)" "cat >> ~/.ssh/authorized_keys"
ssh -p $env:REMOTE_PORT "$($env:REMOTE_USER)@$($env:REMOTE_HOST)" "chmod 600 ~/.ssh/authorized_keys"

Info

It's now possible to login without entering password using ssh -p <port> <user>@<host>

Configure Firewall (ufw)

The goal of a firewall on Linux is to control and manage network traffic based on predetermined security rules. It acts as a barrier between a trusted internal network and untrusted external networks, such as the internet.

Install ufw Firewall

ufw is a straightforward command-line tool that manages iptables configurations, the core firewall infrastructure of Linux. Docker also interacts with these iptables rules, often overriding ufw settings and enforcing its own rules.

# install ufw firewall
sudo apt-get install ufw
Docker and ufw

If you are using the Uncomplicated Firewall (ufw) alongside Docker, you may encounter situations where your Dockerized web applications remain accessible from the internet despite ufw settings. This issue arises because both ufw and Docker manipulate the same underlying iptables configurations, potentially leading to conflicting rules that expose Docker containers regardless of ufw's active state.

A recommended approach involves prioritizing the ufw rules over those of Docker. This can be accomplished using the following command lines:

# force ufw rules before docker's
sudo tee -a /etc/ufw/after.rules > /dev/null <<EOF
# Put Docker behind UFW
*filter
:DOCKER-USER - [0:0]
:ufw-user-input - [0:0]

-A DOCKER-USER -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A DOCKER-USER -m conntrack --ctstate INVALID -j DROP
-A DOCKER-USER -i eth0 -j ufw-user-input
-A DOCKER-USER -i eth0 -j DROP
COMMIT
EOF

# restart and reload ufw rules
sudo ufw reload

Configuring ufw Firewall

The following commands will configure the Linux firewall.

# clean ufw rules
sudo ufw reset                       # reset all firewall settings
sudo ufw disable                     # disable firewall before blocking connexions => otherwise ssh will stop
sudo ufw default deny incoming       # deny all incoming connexions by default

# exposes ports of containers and hosts for specific port
sudo ufw allow 80/tcp                # allow http requests
sudo ufw allow 443/tcp               # allow https requests
sudo ufw allow 445/tcp               # allow samba requests
sudo ufw allow 22/tcp                # allow ssh requests

# activate the new ufw rules
sudo ufw enable                      # enable the firewall with new rules
sudo ufw status verbose              # show firewall status
Protect Against Brute-Force Attacks (fail2ban)

Fail2ban is a lightweight intrusion prevention tool.
It monitors service logs (like SSH) and automatically bans IP addresses that show malicious signs, such as too many failed login attempts in a short time.
This prevents brute-force attacks from overwhelming your server with endless authentication attempts.

Install fail2ban

# install fail2ban
sudo apt-get install -y fail2ban

Configure fail2ban for SSH

# configure global fail2ban settings (applies to all jails)
sudo tee /etc/fail2ban/fail2ban.local > /dev/null <<EOF
[DEFAULT]
allowipv6 = auto
EOF

# configure a dedicated jail for SSH protection
sudo tee /etc/fail2ban/jail.local > /dev/null <<EOF
[sshd]
enabled   = true
port      = 22
logpath   = /var/log/auth.log
maxretry  = 3
findtime  = 10m
bantime   = 24h
ignoreip  = 127.0.0.1/8 ::1 192.168.0.0/16 10.0.0.0/8
EOF

# enable and start the service
sudo systemctl enable --now fail2ban

# reload after changes to jail.local
sudo systemctl restart fail2ban

# check that the service is running
sudo systemctl status fail2ban

Verify and monitor bans

# show current status of the SSH jail (number of failures, active bans, etc.)
sudo fail2ban-client status sshd

# monitor authentication log for incoming attempts
sudo tail -n 25 /var/log/auth.log

# monitor fail2ban's own actions (bans, unbans, warnings)
sudo tail -n 25 /var/log/fail2ban.log

💾 Configure SSD

Detect SSD device
# detect device name
lsblk
*NAME         MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
sda            8:0    0 465.8G  0 disk 
└─sda1         8:1    0 465.8G  0 part 
mmcblk1      179:0    0 116.5G  0 disk 
└─mmcblk1p1  179:1    0 115.3G  0 part /

Tip

The SSD device name here is: /dev/sda.

Format SSD in ext4
# install parted
sudo apt-get install parted

# create a GPT partition table 
sudo parted <device-name> --script -- mklabel gpt

# create ext4 partition that takes the whole space
sudo parted <device-name> --script -- mkpart primary ext4 0% 100%

# format partition to ext4
sudo mkfs.ext4 -F <device-name>
sync

# verify the partition table
sudo parted <device-name> --script print

Tip

Replace <device-name> with your sd-card device name (ex: /dev/sda).

Mount SSD automatically

The following steps describe how to mount automatically the SSD using its UUID.

# install setfacl/getfacl packages
sudo apt-get install acl

# get blkid and save UUID
sudo blkid

# mount automatically
sudo mkdir -p <ssd_dir>
echo 'UUID=<UUID-VALUE> <ssd_dir> ext4 defaults,noatime,nodiratime,commit=60,errors=remount-ro,acl,user,exec 0 2' | sudo tee -a /etc/fstab > /dev/null

Replace the following fields

Field Description
<ssd_dir> the directory where the SSD will be mounted (ex: /media/ssd)
<UUID-VALUE> the UUID value (without quotes) of the SSD read by blkid program
Configure TRIM for SSD/eMMC Devices

eMMC and SSD storage have a limited number of write cycles. Without proper maintenance, excessive write operations can shorten their lifespan, potentially leading to data corruption or device failure. Enabling TRIM helps the operating system inform the storage device which blocks of data are no longer in use, allowing it to manage wear more efficiently and extend the device’s durability.

# configure fstrim for all devices that support it
sudo tee /etc/cron.weekly/fstrim > /dev/null <<EOF
#!/bin/sh
/sbin/fstrim --all || true
exit 0
EOF
sudo chmod +x /etc/cron.weekly/fstrim
Optimize Filesystem and Memory Settings for SSD/eMMC Devices

These system tweaks are recommended to reduce unnecessary write operations on eMMC or SSD devices, extending their lifespan and improving performance:

  • Disable Swap: Sets vm.swappiness=0 to avoid using swap space on eMMC.
  • Suppress Access Time Updates: Adds nodiratime to reduce metadata writes for directory access.
  • Extend Commit Interval: Sets commit=60 to flush metadata changes every 60 seconds instead of more frequently.
# minimize swapping but avoid sudden OOM when RAM is full (1 instead of 0)
echo "vm.swappiness=1" | sudo tee /etc/sysctl.d/99-swappiness.conf > /dev/null

# always allow memory allocations (risk: possible sudden OOM)
echo "vm.overcommit_memory=1" | sudo tee /etc/sysctl.d/99-overcommit.conf > /dev/null

# reload sysctl configs
sudo sysctl --system

# disable access time on files and directories
sudo sed -i 's/noatime,commit/noatime,nodiratime,commit/g' /etc/fstab

# reduce frequency of metadata writing to 60s
sudo sed -i 's/,commit=[0-9]\+/,commit=60/g' /etc/fstab