Build scripts and Docker configuration for running a private LTE network using PlutoPlus SDR and srsRAN on Raspberry Pi 5.
- Raspberry Pi 5 (4GB+ RAM recommended)
- PlutoPlus SDR with custom firmware (timestamping support)
- Docker and Docker Compose (for containerized deployment)
- Programmable USIM cards (e.g., sysmoISIM-SJA5) with matching IMSI/Ki/OPc values
- USB smart card reader for SIM programming (e.g., Omnikey 3121)
- Unlocked phones with Band 13 support (all Verizon phones)
# Build the Docker image (takes 30-60 minutes)
./scripts/build-docker.sh
# Check PlutoSDR connectivity
./scripts/run.sh check-pluto
# Run the full LTE stack
./scripts/run.sh docker-combined# Build all components natively
./scripts/build-native.sh all
# Source the environment
source /etc/profile.d/pluto-lte.sh
# Copy configs
sudo mkdir -p /etc/srsran
sudo cp configs/* /etc/srsran/
# Run the stack
./scripts/run.sh native-allThis network is configured for Band 13 (700 MHz), Verizon's primary LTE band. All Verizon-sold phones support Band 13, making it the best choice for using US Verizon handsets on a private LTE network.
The MCC/MNC values must match across all three places:
| File | Fields | Current Value |
|---|---|---|
configs/enb.conf |
mcc, mnc |
001 / 01 |
configs/epc.conf |
mcc, mnc |
001 / 01 |
configs/user_db.csv |
IMSI prefix | 00101 |
001/01 is a test PLMN, suitable for lab and development use. If you change
the MCC/MNC, you must update all three files and reprogram your SIM cards
to match (see Programming SIM Cards).
Configure your PlutoSDR with a static IP via its config.txt:
[USB_ETHERNET]
ipaddr_eth = 192.168.2.1
netmask_eth = 255.255.255.0
| Parameter | Value | Description |
|---|---|---|
dl_earfcn |
5230 |
Band 13 downlink center (751 MHz) |
n_prb |
25 |
5 MHz bandwidth (25 resource blocks). Band 13 is 10 MHz wide, so 50 PRBs is possible if the SDR/CPU can handle it. |
tx_gain |
89 |
Transmit gain (dB). Near max for PlutoPlus. Reduce for close-range testing. |
rx_gain |
20 |
Receive gain (dB). Increase if UEs have trouble attaching. |
pci |
1 |
Physical Cell Identity (0-503). Only matters if running multiple cells. |
tac |
0x0007 |
Tracking Area Code. Must match epc.conf. |
The device_args line controls how srsRAN talks to the PlutoPlus:
device_args = driver=plutosdr,hostname=192.168.2.1,direct=1,timestamp_every=5760,loopback=0hostname=192.168.2.1- PlutoSDR IP address (USB default)direct=1- Bypass RF switch matrix for lower latencytimestamp_every=5760- IQ timestamp interval (critical for sync)loopback=0- Disable internal loopback (set to1for testing without antenna)
| Parameter | Value | Description |
|---|---|---|
mcc / mnc |
001 / 01 |
Must match enb.conf |
tac |
0x0007 |
Must match enb.conf |
apn |
internet |
Access Point Name. Phones must be configured with this APN. |
dns_addr |
8.8.8.8 |
DNS server pushed to UEs |
encryption_algo |
EEA0 |
No encryption (simplest). Use EEA2 (AES) for security. |
integrity_algo |
EIA1 |
SNOW 3G integrity. EIA2 (AES) is also supported. |
sgi_if_addr |
172.16.0.1 |
IP gateway for the LTE network |
db_file |
/etc/srsran/user_db.csv |
Path to subscriber database |
Each line defines one subscriber (SIM card) the network will accept:
ue1,mil,001010123456789,00112233445566778899aabbccddeeff,opc,63bfa50ee6523365ff14c1f45f88737d,8000,000000001234,9,dynamic| Field | Example | Description |
|---|---|---|
| Name | ue1 |
Human-readable label |
| Auth | mil |
Authentication algorithm (mil = Milenage, xor = XOR) |
| IMSI | 001010123456789 |
15-digit subscriber identity. First 5 digits = MCC+MNC (00101). |
| Ki | 00112233...eeff |
128-bit authentication key (32 hex chars) |
| OP_Type | opc |
opc (pre-computed) or op (raw operator code) |
| OPc | 63bfa50e...737d |
128-bit operator code (32 hex chars) |
| AMF | 8000 |
Authentication Management Field |
| SQN | 000000001234 |
Sequence number (12 hex chars). Increments with each auth. |
| QCI | 9 |
QoS class (9 = default internet, 1 = voice, 5 = IMS signaling) |
| IP_alloc | dynamic |
dynamic for DHCP-style, or a static IP like 172.16.0.2 |
Important: The Ki and OPc values here must exactly match what is programmed onto the physical SIM card. If they don't match, authentication will fail and the UE will not attach.
| Band | dl_earfcn | Frequency | Bandwidth | Notes |
|---|---|---|---|---|
| 13 | 5230 | 751 MHz | 10 MHz | Current config. Verizon primary band. All Verizon phones. |
| 2 | 900 | 1960 MHz | 20 MHz | PCS. Widely supported on all US carriers. |
| 4 | 2175 | 2132.5 MHz | 20 MHz | AWS. Widely supported. |
| 5 | 2525 | 881 MHz | 10 MHz | Cellular 850 MHz. Good building penetration. |
| 48 | 55990 | 3625 MHz | 10 MHz | CBRS. Legal for private LTE (GAA). Requires SAS & newer phones. |
| Band | dl_earfcn | Frequency | Notes |
|---|---|---|---|
| 3 | 1575 | 1842.5 MHz | Common in Europe/Asia. Not on US phones. |
| 7 | 3100 | 2655 MHz | Higher bandwidth, common in Europe. |
To change bands, edit dl_earfcn in configs/enb.conf. No other config changes
are needed.
You need programmable SIM cards to use with this network. Retail carrier SIMs (Verizon, AT&T, etc.) will not work because their authentication keys are locked and unknown.
- Programmable USIM cards - Gialer Programmable SIM card Gialer Programmable Sim.
- Smart card reader - Any PC/SC-compatible USB reader. Common choices:
- Omnikey 3121 (reliable, widely available)
- ACS ACR38 / ACR39
- Generic "USB Smart Card Reader" from Amazon (usually works)
- pySim - PySim Open-source SIM programming tool
# Install system dependencies
sudo apt install python3 python3-pip python3-venv \
pcscd pcsc-tools libpcsclite-dev swig
# Start the smart card daemon
sudo systemctl enable --now pcscd
# Clone and install pySim
git clone https://gitea.osmocom.org/sim-card/pysim.git
cd pysim
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txtVerify your card reader is detected:
pcsc_scan
# Should show your reader and detect an inserted cardEach SIM card must be programmed with values that match a row in user_db.csv.
cd pysim
source venv/bin/activate
./pySim-prog.py \
-p 0 \
-a <ADM_KEY> \
--mcc 001 \
--mnc 01 \
--imsi 001010123456789 \
-k 00112233445566778899aabbccddeeff \
--opc 63bfa50ee6523365ff14c1f45f88737d \
--acc 0001| Flag | Description |
|---|---|
-p 0 |
Card reader index (usually 0 for a single reader) |
-a <ADM_KEY> |
ADM key printed on your sysmocom SIM card carrier |
--mcc / --mnc |
Must match enb.conf and epc.conf |
--imsi |
Full 15-digit IMSI. First 5 digits must be MCC+MNC (00101). |
-k |
Ki (authentication key). Must match user_db.csv. |
--opc |
OPc value. Must match user_db.csv. |
--acc |
Access control class (0001 allows normal access) |
pySim-shell gives you more control and is better for inspecting or modifying individual fields on an already-programmed card:
cd pysim
source venv/bin/activate
./pySim-shell.py -p 0
# Inside the pySim-shell REPL:
# Verify current card contents
pySIM-shell> select ADF.USIM
pySIM-shell> select EF.IMSI
pySIM-shell> read_binary_decoded
# Write IMSI
pySIM-shell> select MF
pySIM-shell> verify_adm <ADM_KEY>
pySIM-shell> select ADF.USIM
pySIM-shell> select EF.IMSI
pySIM-shell> update_binary_decoded '{"imsi": "001010123456789"}'
# Write PLMN selector (so the phone prefers your network)
pySIM-shell> select EF.PLMNsel
pySIM-shell> update_binary_decoded '[{"mcc": "001", "mnc": "01"}]'
# Write OPc
pySIM-shell> select ADF.USIM
pySIM-shell> select EF.OPc
pySIM-shell> update_binary_decoded '{"opc": "63bfa50ee6523365ff14c1f45f88737d"}'For multiple subscribers, program each card with a unique IMSI (and optionally
unique Ki/OPc), and add a matching row in user_db.csv:
# Card 1
./pySim-prog.py -p 0 -a <ADM_KEY> \
--mcc 001 --mnc 01 \
--imsi 001010123456789 \
-k 00112233445566778899aabbccddeeff \
--opc 63bfa50ee6523365ff14c1f45f88737d \
--acc 0001
# Card 2
./pySim-prog.py -p 0 -a <ADM_KEY> \
--mcc 001 --mnc 01 \
--imsi 001010123456790 \
-k 00112233445566778899aabbccddeeff \
--opc 63bfa50ee6523365ff14c1f45f88737d \
--acc 0001The corresponding user_db.csv entries:
ue1,mil,001010123456789,00112233445566778899aabbccddeeff,opc,63bfa50ee6523365ff14c1f45f88737d,8000,000000001234,9,dynamic
ue2,mil,001010123456790,00112233445566778899aabbccddeeff,opc,63bfa50ee6523365ff14c1f45f88737d,8000,000000001235,9,dynamicTip: Using unique Ki/OPc per card is more secure. For lab testing, sharing keys across cards is fine and simplifies management.
- Unlock the phone - The phone must accept non-Verizon SIMs. Verizon automatically unlocks phones after 60 days. You can check in Settings > About Phone > SIM Status.
- Insert the programmed SIM - Replace the Verizon SIM with your programmable USIM.
- Set the APN - Go to Settings > Network > Access Point Names and create
a new APN:
- Name:
Private LTE - APN:
internet(must matchapninepc.conf) - Leave all other fields at default
- Name:
- Select the network - Go to Settings > Network > Mobile Network >
Network Operators > Search Networks. Select the test network (
001 01or "Test Network"). - Verify attachment - Check
/tmp/epc.logon the server side for successful attach messages. The phone should get an IP in the172.16.0.0/24range.
# Check network connectivity
ping 192.168.2.1
# Check USB connection
lsusb | grep Analog
# Check iio connectivity
iio_info -u ip:192.168.2.1- Reduce
n_prbvalue (try 15 or 6) - Ensure PlutoPlus has timestamp-enabled firmware
- Check for CPU throttling:
vcgencmd measure_temp
- Verify IMSI/Ki/OPc match between SIM and
user_db.csv - Check
dl_earfcnmatches your SIM's allowed bands - Review logs:
/tmp/enb.log,/tmp/epc.log
Expected throughput with PlutoPlus SDR (25 PRB):
- Downlink: ~17 Mbps
- Uplink: ~9 Mbps
├── Dockerfile # Multi-stage build for ARM64
├── docker-compose.yml # Container orchestration
├── configs/
│ ├── enb.conf # eNodeB configuration
│ ├── epc.conf # EPC/Core configuration
│ ├── ue.conf # UE configuration (for testing)
│ └── user_db.csv # Subscriber database
└── scripts/
├── build-docker.sh # Docker build helper
├── build-native.sh # Native build script
├── entrypoint.sh # Container entrypoint
└── run.sh # Run helper script
This project assembles open-source components:
- srsRAN: AGPLv3
- SoapySDR: Boost Software License
- LibIIO: LGPLv2.1
-- 2026 - Gordon Young - gjyoung1974@gmail.com