From a9e5fa79a55694bc28e4d1256b47a869cf6ab988 Mon Sep 17 00:00:00 2001 From: Fabian Hauser Date: Mon, 23 Dec 2019 03:18:00 +0100 Subject: [PATCH] Add wwan module --- host/achiles.nix | 8 +- role/networking/wwan.nix | 126 ++++++++++++ role/networking/wwan/mbim-ip.bash | 319 ++++++++++++++++++++++++++++++ 3 files changed, 452 insertions(+), 1 deletion(-) create mode 100644 role/networking/wwan.nix create mode 100644 role/networking/wwan/mbim-ip.bash diff --git a/host/achiles.nix b/host/achiles.nix index 8891b59..3946814 100644 --- a/host/achiles.nix +++ b/host/achiles.nix @@ -30,6 +30,7 @@ in ../hardware/apu.nix ../hardware/wle900vx.nix ../role/base.nix + ../role/wwan.nix (import ../role/router.nix routerConfig) ]; @@ -43,6 +44,11 @@ in [ { device = "/dev/disk/by-uuid/73f91e99-d856-4504-b6b2-d60f855d6d95"; } ]; + services.wwan = { + apn = "gprs.swisscom.ch"; + networkInterface = "wwp0s19u1u3i12"; + }; + # Use the GRUB 2 boot loader. boot.loader.grub.enable = true; boot.loader.grub.version = 2; @@ -85,6 +91,6 @@ in # compatible, in order to avoid breaking some software such as database # servers. You should change this only after NixOS release notes say you # should. - system.stateVersion = "19.03"; # Did you read the comment? + system.stateVersion = "19.09"; # Did you read the comment? } diff --git a/role/networking/wwan.nix b/role/networking/wwan.nix new file mode 100644 index 0000000..121dcb3 --- /dev/null +++ b/role/networking/wwan.nix @@ -0,0 +1,126 @@ +# Based on https://github.com/jgillich/nixos/blob/master/services/ppp.nix +# Tipps and tricks under https://www.hackster.io/munoz0raul/how-to-use-gsm-3g-4g-in-embedded-linux-systems-9047cf#toc-configuring-the-ppp-files-5 +# TODO: http://www.embeddedpi.com/documentation/3g-4g-modems/raspberry-pi-sierra-wireless-mc7455-modem-raw-ip-qmi-interface-setup +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.wwan; + mbim-ip = pkgs.writeScriptBin "mbim-ip" readFile ./wwan/mbim-ip.bash; + mbim-ip-configured = pkgs.writeScriptBin "mbim-ip-configured" '' + #!${pkgs.stdenv.shell} + MBIM_BINARY=${pkgs.libmbim}/bin/mbimcli + MBIM_INTERFACE=${cfg.mbimInterface} + exec ${mbim-ip} $@ + ''; +in +{ + options = { + services.wwan = { + enable = mkEnableOption "wwan client service"; + + config = mkOption { + type = types.attrsOf (types.submodule ( + { + options = { + apn = mkOption { + type = types.str; + description = '' + APN domain of provider. + ''; + }; + + apnUser = mkOption { + type = types.str; + default = ""; + description = '' + APN username (optional). + ''; + }; + + apnPass = mkOption { + type = types.str; + default = ""; + description = '' + APN password (optional). + ''; + }; + + apnAuth = mkOption { + type = types.enum; + values = [ "PAP" "CHAP" "MSCHAPV2" "" ]; + default = ""; + description = '' + APN authentication type, one of ${concatMapStringsSep ", " show values} (optional). + ''; + }; + + mbimProxy = mkOption { + type = types.bool; + default = true; + description = '' + Whether to use the mbim proxy or not. + ''; + }; + + mbimInterface = mkOption { + type = types.path; + default = /dev/cdc-wdm0 + description = "MBIM Interface which the connection will use."; + }; + + networkInterface = mkOption { + type = types.str; + description = "Name of the WWAN network interface"; + }; + }; + } + )); + + default = {}; + + example = literalExample '' + { + wwan = { + apn = "gprs.swisscom.ch"; + networkInterface = "wwp0s19u1u3i12"; + }; + } + ''; + + description = '' + Configuration for WWAN connectivity using a MBIM capable card. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + systemd.services."wwan" = { + description = "WWAN connectivity"; + wantedBy = [ "network.target" ]; + wants = [ "sys-subsystem-net-devices-${cfg.networkInterface}.device"]; + + serviceConfig = { + ExecStart = "@${pkgs.libmbim}/bin/mbim-network ${toString cfg.mbimInterface} start"; + ExecStop = "@${pkgs.libmbim}/bin/mbim-network ${toString cfg.mbimInterface} stop"; + + # MBIM networking is a special fellow - it gets the IP address for us, + # but we need to manually set it to the interface + ExecStartPost = "@${mbim-ip-configured} start ${cfg.networkInterface}"; + ExecStopPre = "@${mbim-ip-configured} stop ${cfg.networkInterface}"; + + RemainAfterExit = true; + }; + }; + + environment.etc."/etc/mbim-network.conf".text = '' + APN=${cfg.apnUser} + APN_USER=${cfg.apnUser} + APN_PASS=${cfg.apnPass} + APN_AUTH=${cfg.apnAuth} + PROXY=${optionalString cfg.proxy "yes"} + ''; + }; +} diff --git a/role/networking/wwan/mbim-ip.bash b/role/networking/wwan/mbim-ip.bash new file mode 100644 index 0000000..6413b37 --- /dev/null +++ b/role/networking/wwan/mbim-ip.bash @@ -0,0 +1,319 @@ +#!/usr/bin/env bash +############################################################################### +# Configuration +############################################################################### +MODE=$1 +DEV=$2 + +if [ "$DEBUG" == "" ]; then + DEBUG="false" +fi + +if [ "$MBIM_INTERFACE" == "" ]; then + MBIM_INTERFACE="/dev/cdc-wdm0" +fi + +if [ "$MBIM_BINARY" == "" ]; then + MBIM_BINARY=mbimcli +fi + +############################################################################### +# Global Variables +############################################################################### +previous_state="none" +state="none" +skip_line=0 +ipv4_addresses=() +ipv4_gateway="" +ipv4_dns=() +ipv4_mtu="" +ipv6_addresses=() +ipv6_gateway="" +ipv6_dns=() +ipv6_mtu="" + +############################################################################### +# Function +############################################################################### + +function print_debug { + if [ "$DEBUG" != "false" ]; then + echo "[State: $state] $1" >&2 + fi +} + +function print_full_configuration { + if [[ "${#ipv4_addresses[@]}" > 0 ]]; then + printf "IPv4: " + printf '%s, ' "${ipv4_addresses[@]}" + printf "\n" + + printf "GW: $ipv4_gateway\n" + + printf "DNS: " + printf '%s, ' "${ipv4_dns[@]}" + printf "\n" + + printf "MTU: $ipv4_mtu\n" + fi + + if [[ "${#ipv6_addresses[@]}" > 0 ]]; then + echo + printf "IPv6: " + printf '%s, ' "${ipv6_addresses[@]}" + printf "\n" + + printf "GW: $ipv6_gateway\n" + + printf "DNS: " + printf '%s, ' "${ipv6_dns[@]}" + printf "\n" + + printf "MTU: $ipv6_mtu\n" + fi +} + +function next_state { + previous_state="$state" + state="$1" +} + +function parse_ip { + # IP [0]: '10.134.203.177/30' + local line_re="IP \[([0-9]+)\]: '(.+)'" + local input=$1 + if [[ $input =~ $line_re ]]; then + local ip_cnt=${BASH_REMATCH[1]} + local ip=${BASH_REMATCH[2]} + fi + echo "$ip" +} + +function parse_dns { + # IP [0]: '10.134.203.177/30' + local line_re="DNS \[([0-9]+)\]: '(.+)'" + local input=$1 + if [[ $input =~ $line_re ]]; then + local dns_cnt=${BASH_REMATCH[1]} + local dns=${BASH_REMATCH[2]} + fi + echo "$dns" +} + +function parse_gateway { + # Gateway: '10.134.203.178' + local line_re="Gateway: '(.+)'" + local input=$1 + if [[ $input =~ $line_re ]]; then + local gw=${BASH_REMATCH[1]} + fi + echo "$gw" +} + +function parse_mtu { + # MTU: '1500' + local line_re="MTU: '([0-9]+)'" + local input=$1 + if [[ $input =~ $line_re ]]; then + local mtu=${BASH_REMATCH[1]} + fi + echo "$mtu" +} + +function parse_input_state_machine { + state="start" + while true; do + if [[ "$skip_line" == 0 ]]; then + read line + else + skip_line=0 + fi + case "$state" in + "start") + read line # first line is empty, read a new one #TODO: This is not very clean... + case "$line" in + *"configuration available: 'none'"*) + # Skip none state + # TODO: This is a workaround of the original parser's shortcomming + continue + ;; + *"IPv4 configuration available"*) + next_state "ipv4_ip" + continue + ;; + *"IPv6 configuration available"*) + next_state "ipv6_ip" + continue + ;; + *) + next_state "exit" + continue + ;; + esac + ;; + "error") + echo "Error in pattern matchin of state $previous_state. Exiting." >&2 + exit 2 + ;; + "exit") + break + ;; + "ipv4_ip") + ipv4=$(parse_ip "$line") + if [ -z "$ipv4" ]; then + if [[ "${#ipv4_addresses[@]}" < 1 ]]; then + next_state "error" + continue + else + next_state "ipv4_gateway" + skip_line=1 + continue + fi + fi + print_debug "$ipv4" + ipv4_addresses+=("$ipv4") + ;; + "ipv4_gateway") + gw=$(parse_gateway "$line") + if [ -z "$gw" ]; then + next_state "error" + continue + fi + print_debug "$gw" + ipv4_gateway="$gw" + next_state "ipv4_dns" + ;; + "ipv4_dns") + ipv4=$(parse_dns "$line") + if [ -z "$ipv4" ]; then + if [[ "${#ipv4_dns[@]}" < 1 ]]; then + next_state "error" + continue + else + next_state "ipv4_mtu" + skip_line=1 + continue + fi + fi + print_debug "$ipv4" + ipv4_dns+=("$ipv4") + ;; + "ipv4_mtu") + mtu=$(parse_mtu "$line") + if [ -z "$mtu" ]; then + next_state "error" + continue + fi + print_debug "$mtu" + ipv4_mtu="$mtu" + next_state "start" + ;; + "ipv6_ip") + ipv6=$(parse_ip "$line") + if [ -z "$ipv6" ]; then + if [[ "${#ipv6_addresses[@]}" < 1 ]]; then + next_state "error" + continue + else + next_state "ipv6_gateway" + skip_line=1 + continue + fi + fi + print_debug "$ipv6" + ipv6_addresses+=("$ipv6") + ;; + "ipv6_gateway") + gw=$(parse_gateway "$line") + if [ -z "$gw" ]; then + next_state "error" + continue + fi + print_debug "$gw" + ipv6_gateway="$gw" + next_state "ipv6_dns" + ;; + "ipv6_dns") + ipv6=$(parse_dns "$line") + if [ -z "$ipv6" ]; then + if [[ "${#ipv6_dns[@]}" < 1 ]]; then + next_state "error" + continue + else + next_state "ipv6_mtu" + skip_line=1 + continue + fi + fi + print_debug "$ipv6" + ipv6_dns+=("$ipv6") + ;; + "ipv6_mtu") + mtu=$(parse_mtu "$line") + if [ -z "$mtu" ]; then + next_state "error" + continue + fi + print_debug "$mtu" + ipv6_mtu="$mtu" + next_state "start" + ;; + *) + print_debug "Invalid state (came from $previous_state). Exiting." + exit 0 + ;; + esac + done +} + + +interface_start(){ + ip addr flush dev $DEV + ip route flush dev $DEV + + ip -6 addr flush dev $DEV + ip -6 p route flush dev $DEV + + #TODO: Nameserver? +} + +interface_stop() { + ip link set $DEV up + + if [[ "${#ipv4_addresses[@]}" > 0 ]]; then + ip addr add ${ipv4_addresses[@]} dev $DEV broadcast + #TODO: Works for multiple addresses? + ip link set $DEV mtu $ipv4_mtu + ip route add default via $ipv4_gateway dev $DEV + #TODO: nameserver ${ipv4_dns[@]} + fi + + if [[ "${#ipv6_addresses[@]}" > 0 ]]; then + ip -6 addr add ${ipv6_addresses[@]} dev $DEV #TODO: Works for multiple addresses? + ip -6 route add default via $ipv6_gateway dev $DEV + ip -6 link set $DEV mtu $ipv6_mtu + #TODO: nameserver ${ipv6_dns[@]}" + fi +} + +############################################################################### +# Execution +############################################################################### +set -x +echo "NOTE: This script does not yet support nameserver configuration." + +parse_input_state_machine <(${MBIM_BINARY} -d ${MBIM_INTERFACE} -p --query-ip-configuration=0) +print_full_configuration + +case "$MODE" in + "start") + interface_stop + ;; + "stop") + interface_start + ;; + *) + echo "USAGE: $0 start|stop INTERFACE" >&2 + echo "You can set an env variable DEBUG to gather debugging output." >&2 + exit 1 + ;; +esac \ No newline at end of file