[06/29] network: Add support for QMI modems

Message ID 20221201172318.3166615-6-michael.tremer@ipfire.org
State Accepted
Commit 957863f754deb9351bd1c53c16f53ef84ac86add
Headers
Series [01/29] libgudev: New package |

Commit Message

Michael Tremer Dec. 1, 2022, 5:22 p.m. UTC
  QMI is a proprietary interface from Qualcomm which are absolute pioneers
when it comes to interfacing with modems. I don't think there would be
any way to make this any more complicated and bloated.

So, bascially we will put the modem into a raw IP mode which changes the
interface into Point-to-Point mode.

We then configure the provider settings using qmicli. After that, the
modem will try to connect to the provider and obtain an IP address.

We will then start a DHCP client which does not do any DHCP-ing because
implementing that would be too complicated. Instead we do something even
*more* complicated where we would launch a custom script which asks the
modem for the allocated IP address and will configure it into the
device. The DHCP client then reads that IP address from the device and
pretends it came up with it by itself. Such an easy way to do this.

Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
---
 src/initscripts/networking/dhcpcd.exe        | 62 ++++++++++++++
 src/initscripts/networking/functions.network | 90 ++++++++++++++++++++
 src/initscripts/networking/red               | 36 ++++++++
 3 files changed, 188 insertions(+)
  

Patch

diff --git a/src/initscripts/networking/dhcpcd.exe b/src/initscripts/networking/dhcpcd.exe
index 8a409d010..be6d63708 100644
--- a/src/initscripts/networking/dhcpcd.exe
+++ b/src/initscripts/networking/dhcpcd.exe
@@ -20,6 +20,7 @@ 
 
 . /etc/sysconfig/rc
 . $rc_functions
+. /etc/init.d/networking/functions.network
 
 eval $(/usr/local/bin/readhash /var/ipfire/ethernet/settings)
 
@@ -85,9 +86,70 @@  dhcpcd_down()
 	fi
 }
 
+# Called when dhcpcd relies on a third party to configure an IP address
+dhcpcd_3rdparty() {
+	local qmi_device="$(qmi_find_device "${interface}")"
+
+	if [ -n "${qmi_device}" ]; then
+		setup_qmi "${qmi_device}" || return $?
+	fi
+
+	return 0
+}
+
+setup_qmi() {
+	local device="${1}"
+
+	local address
+	local netmask
+	local gateway
+	local mtu=1500
+
+	local line
+	while read -r line; do
+		# Extract the value
+		value="${line#*: }"
+
+		case "${line}" in
+			*IPv4\ address:*)
+				address="${value}"
+				;;
+			*IPv4\ subnet\ mask:*)
+				netmask="${value}"
+				;;
+			*IPv4\ gateway\ address:*)
+				gateway="${value}"
+				;;
+			*MTU:*)
+				mtu="${value}"
+				;;
+		esac
+	done <<< "$(qmicli --device="${device}" --wds-get-current-settings)"
+
+	if [ -z "${address}" ] || [ -z "${netmask}" ] || [ -z "${gateway}" ]; then
+		logger -p "local0.info" -t "dhcpcd.exe[$$]" \
+			"Could not retrieve all information from the QMI interface"
+		return 1
+	fi
+
+	# Flush any previous configuration
+	ip addr flush dev "${interface}"
+
+	# Configure the IP address
+	ip addr add "${address}/${netmask}" dev "${interface}"
+
+	# Configure the default route
+	ip route add default via "${gateway}" #mtu "${mtu}"
+
+	return 0
+}
+
 case "$reason" in
 BOUND|INFORM|REBIND|REBOOT|RENEW|TIMEOUT|STATIC)	dhcpcd_up;;
 PREINIT|EXPIRE|FAIL|IPV4LL|NAK|RELEASE|STOP)		dhcpcd_down;;
+3RDPARTY)
+	dhcpcd_3rdparty
+	;;
 *)
 	logger -p "local0.info" -t "dhcpcd.exe[$$]" "Unhandled DHCP event: ${reason}"
 	;;
diff --git a/src/initscripts/networking/functions.network b/src/initscripts/networking/functions.network
index f246919de..9698424fd 100644
--- a/src/initscripts/networking/functions.network
+++ b/src/initscripts/networking/functions.network
@@ -169,3 +169,93 @@  dhcpcd_stop() {
 		echo_failure
 	fi
 }
+
+# QMI stuff
+
+qmi_find_device() {
+	local intf="${1}"
+	local _intf
+
+	local path
+	for path in /dev/cdc-*; do
+		if [ -c "${path}" ]; then
+			_intf="$(qmicli --device="${path}" --device-open-proxy --get-wwan-iface)"
+
+			# Check if the interface matches
+			if [ "${intf}" = "${_intf}" ]; then
+				echo "${path}"
+				return 0
+			fi
+		fi
+	done
+
+	# Nothing found
+	return 1
+}
+
+qmi_enable_rawip_mode() {
+	local intf="${1}"
+
+	# Shut down the device first
+	ip link set "${intf}" down &>/dev/null
+
+	echo "Y" > "/sys/class/net/${intf}/qmi/raw_ip"
+}
+
+qmi_configure_apn() {
+	local device="${1}"
+
+	# APN settings
+	local apn="${2}"
+	local auth="${3}"
+	local username="${4}"
+	local password="${5}"
+
+	local args=(
+		# We only support IPv4 right now
+		"ip-type=4"
+	)
+
+	# Set APN
+	if [ -n "${apn}" ]; then
+		args+=( "apn=${apn}" )
+	fi
+
+	# Set auth
+	case "${auth}" in
+		PAP|CHAP)
+			args+=( "auth=${auth}" )
+			;;
+	esac
+
+	# Set username
+	if [ -n "${username}" ]; then
+		args+=( "username=${username}" )
+	fi
+
+	# Set password
+	if [ -n "${password}" ]; then
+		args+=( "password=${password}" )
+	fi
+
+	local _args
+
+	local arg
+	for arg in ${args[@]}; do
+		if [ -n "${_args}" ]; then
+			_args="${_args},"
+		fi
+		_args="${_args}${arg}"
+	done
+
+	qmicli --device="${device}" --device-open-proxy \
+		--wds-start-network="${_args}" \
+		--client-no-release-cid
+}
+
+qmi_reset() {
+	local device="${1}"
+
+	qmicli --device="${device}" --device-open-proxy \
+		--wds-reset
+}
diff --git a/src/initscripts/networking/red b/src/initscripts/networking/red
index fc10e077a..7df61c1cf 100644
--- a/src/initscripts/networking/red
+++ b/src/initscripts/networking/red
@@ -210,6 +210,27 @@  case "${1}" in
 				if [ "$TYPE" == "pptpatm" ]; then
 					TYPE="pptp"
 				fi
+
+			# QMI
+			elif [ "$TYPE" = "qmi" ]; then
+				DEVICE="$(qmi_find_device "${RED_DEV}")"
+
+				boot_mesg "Bringing up QMI on ${RED_DEV} (${DEVICE})..."
+
+				# Enable RAW-IP mode
+				qmi_enable_rawip_mode "${RED_DEV}"
+
+				# Configure APN
+				qmi_configure_apn "${DEVICE}" "${APN}" "${AUTH}" "${USERNAME}" "${PASSWORD}"
+
+				# Set up the interface
+				ip link set "${RED_DEV}" up &>/dev/null
+
+				# Start the DHCP client
+				dhcpcd_start "${RED_DEV}" --debug
+
+				# Done
+				exit 0
 			fi
 
 			if [ "$TYPE" == "vdsl" ]; then
@@ -477,6 +498,21 @@  case "${1}" in
 			run_subdir ${rc_base}/init.d/networking/red.down/
 
 		elif [ "$TYPE" == "PPPOE" ]; then
+			eval $(/usr/local/bin/readhash /var/ipfire/ppp/settings)
+
+			if [ "${TYPE}" = "qmi" ]; then
+				boot_mesg "Bringing down the QMI interface ${RED_DEV}..."
+				DEVICE="$(qmi_find_device "${RED_DEV}")"
+
+				# Stop the DHCP client on RED
+				dhcpcd_stop "${RED_DEV}"
+
+				# Reset any QMI settings
+				qmi_reset "${DEVICE}"
+
+				exit 0
+			fi
+
 			boot_mesg "Bringing down the PPP interface ..."
 			rm -f /var/ipfire/red/keepconnected
 			killall -w -s TERM /usr/sbin/pppd 2>/dev/null