Page MenuHomeVyOS Platform

WWAN modems using raw-ip do not work with dhclient/dhcp6c
Closed, ResolvedPublicBUG

Description

I've been playing around getting a 4G modem (Sierra AirPrime MC7455 3G/4G LTE Modem) working on VyOS with GoogleFi

It all seemed easy, just set the APN and DHCP/DHCPv6

This led me to this forum post talking about this exact issue: https://forum.vyos.io/t/wwan-not-getting-an-ip/7757
It seems that this card does not support a link layer protocol of 802-3 (ethernet), but raw-ip only

We can see this with

root@vyos:# qmicli --device=/dev/cdc-wdm0 --device-open-proxy --wda-get-data-format
[/dev/cdc-wdm0] Successfully got data format
                   QoS flow header: no
               Link layer protocol: 'raw-ip'
  Uplink data aggregation protocol: 'disabled'
Downlink data aggregation protocol: 'disabled'
                     NDP signature: '0'
Downlink data aggregation max datagrams: '0'
Downlink data aggregation max size: '0'

I tried to set 802-3, via qmicli --device=/dev/cdc-wdm0 --device-open-proxy --wda-set-data-format=802-3

root@vyos:~# qmicli --device=/dev/cdc-wdm0 --device-open-proxy --wda-set-data-format=802-3
[/dev/cdc-wdm0] Successfully set data format
                        QoS flow header: no
                    Link layer protocol: 'raw-ip'
       Uplink data aggregation protocol: 'disabled'
     Downlink data aggregation protocol: 'disabled'
                          NDP signature: '0'
Downlink data aggregation max datagrams: '0'
     Downlink data aggregation max size: '0'
  Uplink data aggregation max datagrams: '1'
       Uplink data aggregation max size: '4294967295'

The protocol doesn't change unfortunately, because it's not supported.

Looking more into it, the reason why DHCP doesn't work is because dhclient and dhcp6c doesn't support raw-ip, there seems to be a PR in the works for dhclient but I believe it's stalled and likely won't ever be picked up https://gitlab.isc.org/isc-projects/dhcp/-/merge_requests/67

Jan 21 09:31:14 vyos systemd[1]: Stopped [email protected] - DHCP client on wwan0.
Jan 21 09:31:14 vyos systemd[1]: Starting [email protected] - DHCP client on wwan0...
Jan 21 09:31:14 vyos systemd[1]: Started [email protected] - DHCP client on wwan0.
Jan 21 09:31:14 vyos dhclient[3030]: Internet Systems Consortium DHCP Client 4.4.3-P1
Jan 21 09:31:14 vyos dhclient[3030]: Internet Systems Consortium DHCP Client 4.4.3-P1
Jan 21 09:31:14 vyos dhclient[3030]: Copyright 2004-2022 Internet Systems Consortium.
Jan 21 09:31:14 vyos dhclient[3030]: All rights reserved.
Jan 21 09:31:14 vyos dhclient[3030]: For info, please visit https://www.isc.org/software/dhcp/
Jan 21 09:31:14 vyos dhclient[3030]: Copyright 2004-2022 Internet Systems Consortium.
Jan 21 09:31:14 vyos dhclient[3030]: All rights reserved.
Jan 21 09:31:14 vyos dhclient[3030]: For info, please visit https://www.isc.org/software/dhcp/
Jan 21 09:31:14 vyos dhclient[3030]:
Jan 21 09:31:14 vyos dhclient-script-vyos[3031]: Current dhclient PID: 3030, Parent PID: 1, IP version: 4, All dhclients for interface wwan0: 3030
Jan 21 09:31:14 vyos dhclient-script-vyos[3031]: Passing command to /usr/sbin/ip: "link set dev wwan0 up"
Jan 21 09:31:14 vyos dhclient-script-vyos[3031]: No changes to apply via vyos-hostsd-client
Jan 21 09:31:14 vyos dhclient[3030]: Unsupported device type 65534 for "wwan0"
Jan 21 09:31:15 vyos dhclient[3030]: Unsupported device type 65534 for "wwan0"
Jan 21 09:31:15 vyos dhclient[3030]: If you think you have received this message due to a bug rather
Jan 21 09:31:15 vyos dhclient[3030]: than a configuration issue please read the section on submitting
Jan 21 09:31:15 vyos dhclient[3030]: bugs on either our web page at www.isc.org or in the README file
Jan 21 09:31:15 vyos dhclient[3030]: before submitting a bug.  These pages explain the proper
Jan 21 09:31:15 vyos dhclient[3030]: process and the information we find helpful for debugging.
Jan 21 09:31:15 vyos dhclient[3030]: exiting.
Jan 21 09:31:14 vyos dhclient[3030]:
Jan 21 09:31:14 vyos dhclient[3030]: If you think you have received this message due to a bug rather
Jan 21 09:31:14 vyos dhclient[3030]: than a configuration issue please read the section on submitting
Jan 21 09:31:14 vyos dhclient[3030]: bugs on either our web page at www.isc.org or in the README file
Jan 21 09:31:14 vyos dhclient[3030]: before submitting a bug.  These pages explain the proper
Jan 21 09:31:14 vyos dhclient[3030]: process and the information we find helpful for debugging.
Jan 21 09:31:14 vyos dhclient[3030]:
Jan 21 09:31:15 vyos dhclient[3030]: exiting.
Jan 21 09:31:15 vyos systemd[1]: [email protected]: Main process exited, code=exited, status=1/FAILURE
Jan 21 09:31:15 vyos systemd[1]: [email protected]: Failed with result 'exit-code'.
Jan 21 09:46:03 vyos dhcp6c[6091]: copy_option: set client ID (len 18)
Jan 21 09:46:03 vyos dhcp6c[6091]: copyout_option: set identity association
Jan 21 09:46:03 vyos dhcp6c[6091]: copy_option: set elapsed time (len 2)
Jan 21 09:46:03 vyos dhcp6c[6091]: copy_option: set option request (len 4)
Jan 21 09:46:03 vyos dhcp6c[6091]: client6_send: transmit failed: Cannot assign requested address
Jan 21 09:46:03 vyos dhcp6c[6091]: dhcp6_reset_timer: reset a timer on wwan0, state=SOLICIT, timeo=6, retrans=68413

This is also noted by ModemManager (which is what VyOS uses to handle starting/stopping the connection) - https://modemmanager.org/docs/modemmanager/ip-connectivity-setup-in-lte-modems/

In particular, QMI modems running in raw-ip mode (without ethernet headers in the link layer) will by default be requested to use static IP addressing; not because the DHCP server in the modem doesn’t work, but because not all DHCP clients in Linux can work with network interfaces without ethernet headers (e.g. dhclient can’t).

The modem does have an IP address as mentioned above

vyos@vyos# sudo mmcli -m 0 -b 1
  ------------------------------------
  General            |           path: /org/freedesktop/ModemManager1/Bearer/2
                     |           type: default
  ------------------------------------
  Status             |      connected: yes
                     |      suspended: no
                     |    multiplexed: no
                     |      interface: wwan0
                     |     ip timeout: 20
  ------------------------------------
  Properties         |            apn: h2g2
                     |        roaming: allowed
                     |        ip type: ipv4v6
  ------------------------------------
  IPv4 configuration |         method: static
                     |        address: 26.112.194.94
                     |         prefix: 30
                     |        gateway: 26.112.194.93
                     |            dns: 10.177.0.34, 106.151.0.253
                     |            mtu: 1500
  ------------------------------------
  IPv6 configuration |         method: static
                     |        address: 2607:fb91:ee1:9e39:f488:eb51:81eb:2902
                     |         prefix: 64
                     |        gateway: 2607:fb91:ee1:9e39:a91c:c31f:4e25:a5e2
                     |            dns: fd00:976a::9, fd00:976a::10
                     |            mtu: 1500
  ------------------------------------
  Statistics         |     start date: 2024-01-21T09:44:53Z
                     |       duration: 240
                     |       attempts: 1
                     | total-duration: 240

vyos@vyos# sudo qmicli -d /dev/cdc-wdm0 --device-open-proxy --wds-get-current-settings
[/dev/cdc-wdm0] Current settings retrieved:
           IP Family: IPv4
        IPv4 address: 26.112.194.94
    IPv4 subnet mask: 255.255.255.252
IPv4 gateway address: 26.112.194.93
    IPv4 primary DNS: 10.177.0.34
  IPv4 secondary DNS: 106.151.0.253
                 MTU: 1500
             Domains: none

Another note on the ModemManager page is

ModemManager does not perform the network interface configuration in the host, as it is exclusively in charge of communicating in the signaling plane between host and modem. The network manager process in use in the system will collect the IP settings exposed in the ModemManager Bearer object, and use them to configure the network interface accordingly.

Which leads me to believe NetworkManager will read that IP information (can get JSON output with --output-json) then manually configured the IP/default route. I did statically set the IP/route myself and was able to reach the internet via the 4G connection

I was hoping there is a way to use DHCP to do this still and I stumbled on an article (https://forums.raspberrypi.com/viewtopic.php?t=325044) that mentioning udhcpc would work with raw-ip, so I tried it and can confirm it works!

root@vyos# udhcpc -i wwan0
udhcpc: started, v1.35.0
udhcpc: broadcasting discover
udhcpc: broadcasting select for 26.112.68.182, server 26.112.68.181
udhcpc: lease of 26.112.68.182 obtained from 26.112.68.181, lease time 7200

Alternatively, I tried with systemd-networkd but did not get anywhere, and noticed this comment on an issue (https://github.com/systemd/systemd/issues/27219#issuecomment-1505640023) that seems to say that systemd-networkd does not support raw-ip either

vyos@vyos:~$ show version
Version:          VyOS 1.5-rolling-202401161743
Release train:    current

Built by:         [email protected]
Built on:         Tue 16 Jan 2024 19:41 UTC
Build UUID:       f7976e02-b740-4027-8c96-6335bbea4315
Build commit ID:  365f10340ec2f1

Architecture:     x86_64
Boot via:         installed image
System type:      bare metal

Hardware vendor:  Lanner Electronics Inc.
Hardware model:   NCA-1515B-VS1
Hardware S/N:     LR202112012772
Hardware UUID:    03000200-0400-0500-0006-000700080009

Copyright:        VyOS maintainers and contributors

I believe this issue is not seen on modules/modems that support 802-3 for their link layer protocol as DHCP would work normally as expected in that case. It seems to be specifically related to raw-ip

Details

Difficulty level
Hard (possibly days)
Version
1.5-rolling-202401161743
Why the issue appeared?
Will be filled on close
Is it a breaking change?
Behavior change
Issue type
Improvement (missing useful functionality)

Event Timeline

yzguy updated the task description. (Show Details)

After thinking a bit I suppose there is 3 possible solutions.

  1. Completely replace dhclient with udhcpc
  2. Instead of using dhclient with wwan interfaces, use udhcpc instead, everything else will continue to use dhclient
  3. Read IP information from mmcli --modem 0 -b 2 --output-json and configure the IP on the interface, set static default route. Would also need some cron-like task to keep checking to see if the IP has changed and update it accordingly on the interface/route

It seems systemd-networkd nor dhcpcd supports raw-ip either

dhclient just straight up doesn't work with raw-ip, so there isn't really an event that I can imagine it could trigger off. Even if there was I don't know what we'd even do, I don't think it would be good to have the dhclient hooks be the thing that gets the IP from mmcli -m 0 -b 2 and configures all the associated bits if we went that way.
The wwan0 interface doesn't have a MAC address so dhclient doesn't know what to do

Jan 21 09:31:14 vyos systemd[1]: Stopped [email protected] - DHCP client on wwan0.
Jan 21 09:31:14 vyos systemd[1]: Starting [email protected] - DHCP client on wwan0...
Jan 21 09:31:14 vyos systemd[1]: Started [email protected] - DHCP client on wwan0.
Jan 21 09:31:14 vyos dhclient[3030]: Internet Systems Consortium DHCP Client 4.4.3-P1
Jan 21 09:31:14 vyos dhclient[3030]: Internet Systems Consortium DHCP Client 4.4.3-P1
Jan 21 09:31:14 vyos dhclient[3030]: Copyright 2004-2022 Internet Systems Consortium.
Jan 21 09:31:14 vyos dhclient[3030]: All rights reserved.
Jan 21 09:31:14 vyos dhclient[3030]: For info, please visit https://www.isc.org/software/dhcp/
Jan 21 09:31:14 vyos dhclient[3030]: Copyright 2004-2022 Internet Systems Consortium.
Jan 21 09:31:14 vyos dhclient[3030]: All rights reserved.
Jan 21 09:31:14 vyos dhclient[3030]: For info, please visit https://www.isc.org/software/dhcp/
Jan 21 09:31:14 vyos dhclient[3030]:
Jan 21 09:31:14 vyos dhclient-script-vyos[3031]: Current dhclient PID: 3030, Parent PID: 1, IP version: 4, All dhclients for interface wwan0: 3030
Jan 21 09:31:14 vyos dhclient-script-vyos[3031]: Passing command to /usr/sbin/ip: "link set dev wwan0 up"
Jan 21 09:31:14 vyos dhclient-script-vyos[3031]: No changes to apply via vyos-hostsd-client
Jan 21 09:31:14 vyos dhclient[3030]: Unsupported device type 65534 for "wwan0"
Jan 21 09:31:15 vyos dhclient[3030]: Unsupported device type 65534 for "wwan0"
Jan 21 09:31:15 vyos dhclient[3030]: If you think you have received this message due to a bug rather
Jan 21 09:31:15 vyos dhclient[3030]: than a configuration issue please read the section on submitting
Jan 21 09:31:15 vyos dhclient[3030]: bugs on either our web page at www.isc.org or in the README file
Jan 21 09:31:15 vyos dhclient[3030]: before submitting a bug.  These pages explain the proper
Jan 21 09:31:15 vyos dhclient[3030]: process and the information we find helpful for debugging.
Jan 21 09:31:15 vyos dhclient[3030]: exiting.
Jan 21 09:31:14 vyos dhclient[3030]:
Jan 21 09:31:14 vyos dhclient[3030]: If you think you have received this message due to a bug rather
Jan 21 09:31:14 vyos dhclient[3030]: than a configuration issue please read the section on submitting
Jan 21 09:31:14 vyos dhclient[3030]: bugs on either our web page at www.isc.org or in the README file
Jan 21 09:31:14 vyos dhclient[3030]: before submitting a bug.  These pages explain the proper
Jan 21 09:31:14 vyos dhclient[3030]: process and the information we find helpful for debugging.
Jan 21 09:31:14 vyos dhclient[3030]:
Jan 21 09:31:15 vyos dhclient[3030]: exiting.
Jan 21 09:31:15 vyos systemd[1]: [email protected]: Main process exited, code=exited, status=1/FAILURE
Jan 21 09:31:15 vyos systemd[1]: [email protected]: Failed with result 'exit-code'.

I mentioned above 3 options I thought of and #2 seems realistically the solution that makes sense in my mind as #1 is a huge lift and #3 is a lot of code to "simulate DHCP". If/when dhclient is replaced and the replacement supports raw-ip it should be somewhat easy to take out udhcpc in favor of the replacement

We'd also need to handle DHCPv6 as well as that would hit the same issue

Jan 21 09:46:03 vyos dhcp6c[6091]: copy_option: set client ID (len 18)
Jan 21 09:46:03 vyos dhcp6c[6091]: copyout_option: set identity association
Jan 21 09:46:03 vyos dhcp6c[6091]: copy_option: set elapsed time (len 2)
Jan 21 09:46:03 vyos dhcp6c[6091]: copy_option: set option request (len 4)
Jan 21 09:46:03 vyos dhcp6c[6091]: client6_send: transmit failed: Cannot assign requested address
Jan 21 09:46:03 vyos dhcp6c[6091]: dhcp6_reset_timer: reset a timer on wwan0, state=SOLICIT, timeo=6, retrans=68413

There seems to be some udhcpc6 references when Googling but I can't find a package for it like udhcpc

yzguy renamed this task from WWAN modems using raw-ip do not work with dhclient to WWAN modems using raw-ip do not work with dhclient/dhcp6c.Jan 21 2024, 9:49 AM
yzguy updated the task description. (Show Details)
yzguy updated the task description. (Show Details)

Just as a temporary solution before a long term one is figured out, I did implement this as a post-boot script. For any one who stumbles on this

Grab all the wwan interfaces that are not DOWN, loop through them, check if they are connected, then start udhcpc on them.

wwan_interfaces=$(ip -p -j -br link show | jq -r '.[] | select(.ifname | startswith("wwan")) | select(.operstate != "DOWN") | .ifname')
for wwan_interface in $wwan_interfaces; do
  idx="${wwan_interface: -1}"
  status=$(/usr/bin/mmcli -m $idx -b 1 --output-json | jq '.bearer.status.connected' -r)
  if [ "$status" = "yes" ]; then
    /usr/sbin/udhcpc -i $wwan_interface
  fi
done

Digging into it a little, @c-po mentioned in Slack about trying https://gitlab.isc.org/isc-projects/dhcp/-/merge_requests/67 (the stalled PR). I had to fix a few things which I left as a comment on that PR just in case some else stumbles on it.

WWAN interfaces can use ARPHRD_NONE or ARPHRD_RAWIP, which at least for my modem it was doing ARPHRD_NONE which the PR didn't originally support, so I would see the Unsupported device type 65534 for "wwan0". After adding the support for that + small fixes, the custom version worked as one would expect

Jan 24 03:32:38 vyos dhclient[28382]: Listening on LPF/wwan0/
Jan 24 03:32:38 vyos dhclient[28382]: Sending on   LPF/wwan0/
Jan 24 03:32:38 vyos dhclient[28382]: Sending on   Socket/fallback
Jan 24 03:32:38 vyos dhclient[28382]: DHCPDISCOVER on wwan0 to 255.255.255.255 port 67 interval 2
Jan 24 03:32:38 vyos dhclient[28382]: Received DHCPv4 packet without client-id option and empty hlen field.
Jan 24 03:32:38 vyos dhclient[28382]: DHCPOFFER of 33.191.184.77 from 33.191.184.78
Jan 24 03:32:38 vyos dhclient[28382]: DHCPREQUEST for 33.191.184.77 on wwan0 to 255.255.255.255 port 67
Jan 24 03:32:38 vyos dhclient[28382]: Received DHCPv4 packet without client-id option and empty hlen field.
Jan 24 03:32:38 vyos dhclient[28382]: DHCPACK of 33.191.184.77 from 33.191.184.78

@c-po This would be the commits from https://gitlab.isc.org/isc-projects/dhcp/-/merge_requests/67 and 2 of mine to fix the issues with that PR, plus add support for ARPHRD_NONE

From 8d9e8ace96ad9e2dba9f2d4069228dee5daf6772 Mon Sep 17 00:00:00 2001
From: Loic Poulain <[email protected]>
Date: Mon, 2 Nov 2020 06:42:12 -0500
Subject: [PATCH 1/4] Add support for raw IP interface type
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Under linux some devices can expose raw IP interfaces, such as WWAN
modems. In that case IP data is not encapsulated in any lower level
protocol.

dhclient does not support this currently and this patch adds support
for such pure IP interfaces.

The original patch comes from Bjørn Mork on Network-Manage mailing list:
https://mail.gnome.org/archives/networkmanager-list/2015-December/msg00044.html
---
 common/bpf.c    | 60 ++++++++++++++++++++++++++++++++++++++++++++++++-
 common/lpf.c    | 59 +++++++++++++++++++++++++++++++++++++-----------
 common/packet.c |  7 ++++++
 includes/dhcp.h |  1 +
 4 files changed, 113 insertions(+), 14 deletions(-)

diff --git a/common/bpf.c b/common/bpf.c
index 0bffcbf9..2eb38236 100644
--- a/common/bpf.c
+++ b/common/bpf.c
@@ -198,6 +198,34 @@ struct bpf_insn dhcp_bpf_filter [] = {
 	BPF_STMT (BPF_RET + BPF_K, 0),
 };
 
+int dhcp_bpf_filter_len = sizeof dhcp_bpf_filter / sizeof (struct bpf_insn);
+
+struct bpf_insn dhcp_bpf_pureip_filter [] = {
+	/* Make sure it's a UDP packet... */
+	BPF_STMT (BPF_LD + BPF_B + BPF_ABS, 9),
+	BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 6),
+
+	/* Make sure this isn't a fragment... */
+	BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 6),
+	BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 4, 0),
+
+	/* Get the IP header length... */
+	BPF_STMT (BPF_LDX + BPF_B + BPF_MSH, 0),
+
+	/* Make sure it's to the right port... */
+	BPF_STMT (BPF_LD + BPF_H + BPF_IND, 2),
+	BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, 37, 0, 1),             /* patch */
+
+	/* If we passed all the tests, ask for the whole packet. */
+	BPF_STMT(BPF_RET+BPF_K, (u_int)-1),
+
+	/* Otherwise, drop it. */
+	BPF_STMT(BPF_RET+BPF_K, 0),
+};
+
+int dhcp_bpf_pureip_filter_len =
+	sizeof dhcp_bpf_pureip_filter / sizeof (struct bpf_insn);
+
 #if defined(RELAY_PORT)
 /*
  * For relay port extension
@@ -235,13 +263,43 @@ struct bpf_insn dhcp_bpf_relay_filter [] = {
 
 int dhcp_bpf_relay_filter_len =
 	sizeof dhcp_bpf_relay_filter / sizeof (struct bpf_insn);
+
+struct bpf_insn dhcp_bpf_pureip_relay_filter [] = {
+	/* Make sure it's a UDP packet... */
+	BPF_STMT (BPF_LD + BPF_B + BPF_ABS, 9),
+	BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 8),
+
+	/* Make sure this isn't a fragment... */
+	BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 6),
+	BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 6, 0),
+
+	/* Get the IP header length... */
+	BPF_STMT (BPF_LDX + BPF_B + BPF_MSH, 0),
+
+	/* Make sure it's to the right port... */
+	BPF_STMT (BPF_LD + BPF_H + BPF_IND, 16),
+	BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, 37, 2, 0),             /* patch */
+
+	/* relay can have an alternative port... */
+	BPF_STMT (BPF_LD + BPF_H + BPF_IND, 16),
+	BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, 37, 0, 1),             /* patch */
+
+	/* If we passed all the tests, ask for the whole packet. */
+	BPF_STMT (BPF_RET + BPF_K, (u_int)-1),
+
+	/* Otherwise, drop it. */
+	BPF_STMT (BPF_RET + BPF_K, 0),
+};
+
+int dhcp_bpf_pureip_relay_filter_len =
+	sizeof dhcp_bpf_pureip_relay_filter / sizeof (struct bpf_insn);
+
 #endif
 
 #if defined (DEC_FDDI)
 struct bpf_insn *bpf_fddi_filter = NULL;
 #endif
 
-int dhcp_bpf_filter_len = sizeof dhcp_bpf_filter / sizeof (struct bpf_insn);
 #if defined (HAVE_TR_SUPPORT)
 struct bpf_insn dhcp_bpf_tr_filter [] = {
         /* accept all token ring packets due to variable length header */
diff --git a/common/lpf.c b/common/lpf.c
index bd20b3f5..cffbd853 100644
--- a/common/lpf.c
+++ b/common/lpf.c
@@ -177,9 +177,15 @@ void if_deregister_send (info)
 extern struct sock_filter dhcp_bpf_filter [];
 extern int dhcp_bpf_filter_len;
 
+extern struct sock_filter dhcp_bpf_pureip_filter [];
+extern int dhcp_bpf_pureip_filter_len;
+
 #if defined(RELAY_PORT)
 extern struct sock_filter dhcp_bpf_relay_filter [];
 extern int dhcp_bpf_relay_filter_len;
+
+extern struct sock_filter dhcp_bpf_pureip_relay_filter [];
+extern int dhcp_bpf_pureip_relay_filter_len;
 #endif
 
 #if defined (HAVE_TR_SUPPORT)
@@ -249,31 +255,52 @@ void if_deregister_receive (info)
 static void lpf_gen_filter_setup (info)
 	struct interface_info *info;
 {
+	int pure_ip = info -> hw_address.hbuf [0] == HTYPE_PUREIP;
 	struct sock_fprog p;
 
 	memset(&p, 0, sizeof(p));
 
-	/* Set up the bpf filter program structure.    This is defined in
-	   bpf.c */
-	p.len = dhcp_bpf_filter_len;
-	p.filter = dhcp_bpf_filter;
+	/* Set up the bpf filter program structure and patch port(s).
+	 *
+	 * This is defined in bpf.c, XXX changes to filter program may
+	 * require changes to the insn number(s) used below! XXX
+	 */
+
+	if (pure_ip) {
+		p.len = dhcp_bpf_pureip_filter_len;
+		p.filter = dhcp_bpf_pureip_filter;
+
+		/* patch port */
+		dhcp_bpf_pureip_filter [6].k = ntohs (local_port);
+	} else {
+		p.len = dhcp_bpf_filter_len;
+		p.filter = dhcp_bpf_filter;
+
+		/* patch port */
+		dhcp_bpf_filter [8].k = ntohs (local_port);
+	}
 
-        /* Patch the server port into the LPF  program...
-	   XXX changes to filter program may require changes
-	   to the insn number(s) used below! XXX */
 #if defined(RELAY_PORT)
-	if (relay_port) {
-		/*
-		 * If user defined relay UDP port, we need to filter
-		 * also on the user UDP port.
-		 */
+	/*
+	 * If user defined relay UDP port, we need to filter
+	 * also on the user UDP port.
+	 */
+	if (relay_port && pure_ip) {
+		p.len = dhcp_bpf_pureip_relay_filter_len;
+		p.filter = dhcp_bpf_pureip_relay_filter;
+
+		/* patch ports */
+		dhcp_bpf_pureip_relay_filter [6].k = ntohs (local_port);
+		dhcp_bpf_pureip_relay_filter [8].k = ntohs (relay_port);
+	} else if (relay_port) {
 		p.len = dhcp_bpf_relay_filter_len;
 		p.filter = dhcp_bpf_relay_filter;
 
+		/* patch ports */
+		dhcp_bpf_relay_filter [8].k = ntohs (local_port);
 		dhcp_bpf_relay_filter [10].k = ntohs (relay_port);
 	}
 #endif
-	dhcp_bpf_filter [8].k = ntohs (local_port);
 
 	if (setsockopt (info -> rfdesc, SOL_SOCKET, SO_ATTACH_FILTER, &p,
 			sizeof p) < 0) {
@@ -563,6 +590,12 @@ get_hw_addr(const char *name, struct hardware *hw) {
 			hw->hbuf[0] = HTYPE_FDDI;
 			memcpy(&hw->hbuf[1], sa->sa_data, 6);
 			break;
+#ifdef ARPHRD_RAWIP
+		case ARPHRD_RAWIP:
+			hw->hlen = 1;
+			hw->hbuf[0] = HTYPE_PUREIP;
+			break;
+#endif
 		default:
 			log_fatal("Unsupported device type %ld for \"%s\"",
 				  (long int)sa->sa_family, name);
diff --git a/common/packet.c b/common/packet.c
index 49795c4c..6745db7d 100644
--- a/common/packet.c
+++ b/common/packet.c
@@ -119,6 +119,10 @@ void assemble_hw_header (interface, buf, bufix, to)
 	case HTYPE_INFINIBAND:
 		log_error("Attempt to assemble hw header for infiniband");
 		break;
+	case HTYPE_PUREIP:
+		/* Nothing to do, there is no hw header */
+		*bufix = 0;
+		break;
 	case HTYPE_ETHER:
 	default:
 		assemble_ethernet_header(interface, buf, bufix, to);
@@ -219,6 +223,9 @@ ssize_t decode_hw_header (interface, buf, bufix, from)
 	case HTYPE_INFINIBAND:
 		log_error("Attempt to decode hw header for infiniband");
 		return (0);
+	case HTYPE_PUREIP:
+		/* Nothing to do, there is no hw header */
+		return 0;
 	case HTYPE_ETHER:
 	default:
 		return (decode_ethernet_header(interface, buf, bufix, from));
diff --git a/includes/dhcp.h b/includes/dhcp.h
index cafe1728..02862fba 100644
--- a/includes/dhcp.h
+++ b/includes/dhcp.h
@@ -76,6 +76,7 @@ struct dhcp_packet {
 #define HTYPE_IEEE802	6               /* IEEE 802.2 Token Ring...	*/
 #define HTYPE_FDDI	8		/* FDDI...			*/
 #define HTYPE_INFINIBAND  32		/* IP over Infiniband		*/
+#define HTYPE_PUREIP	35		/* Pure IP			*/
 #define HTYPE_IPMP       255            /* IPMP - random hw address - there
 					 * is no standard for this so we
 					 * just steal a type            */
-- 
2.39.2


From e67d1b6b4178f412084459c4cb7e54a8c0019bd2 Mon Sep 17 00:00:00 2001
From: Francis Dupont <[email protected]>
Date: Fri, 6 Nov 2020 10:46:09 +0100
Subject: [PATCH 2/4] Checkpoint: improved patch

---
 common/bpf.c | 10 +++---
 common/lpf.c | 89 +++++++++++++++++++++++++++++++++++-----------------
 2 files changed, 65 insertions(+), 34 deletions(-)

diff --git a/common/bpf.c b/common/bpf.c
index 2eb38236..b87381ff 100644
--- a/common/bpf.c
+++ b/common/bpf.c
@@ -214,13 +214,13 @@ struct bpf_insn dhcp_bpf_pureip_filter [] = {
 
 	/* Make sure it's to the right port... */
 	BPF_STMT (BPF_LD + BPF_H + BPF_IND, 2),
-	BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, 37, 0, 1),             /* patch */
+	BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, 67, 0, 1),             /* patch */
 
 	/* If we passed all the tests, ask for the whole packet. */
-	BPF_STMT(BPF_RET+BPF_K, (u_int)-1),
+	BPF_STMT(BPF_RET + BPF_K, (u_int)-1),
 
 	/* Otherwise, drop it. */
-	BPF_STMT(BPF_RET+BPF_K, 0),
+	BPF_STMT(BPF_RET + BPF_K, 0),
 };
 
 int dhcp_bpf_pureip_filter_len =
@@ -278,11 +278,11 @@ struct bpf_insn dhcp_bpf_pureip_relay_filter [] = {
 
 	/* Make sure it's to the right port... */
 	BPF_STMT (BPF_LD + BPF_H + BPF_IND, 16),
-	BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, 37, 2, 0),             /* patch */
+	BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, 67, 2, 0),             /* patch */
 
 	/* relay can have an alternative port... */
 	BPF_STMT (BPF_LD + BPF_H + BPF_IND, 16),
-	BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, 37, 0, 1),             /* patch */
+	BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, 67, 0, 1),             /* patch */
 
 	/* If we passed all the tests, ask for the whole packet. */
 	BPF_STMT (BPF_RET + BPF_K, (u_int)-1),
diff --git a/common/lpf.c b/common/lpf.c
index cffbd853..0497309f 100644
--- a/common/lpf.c
+++ b/common/lpf.c
@@ -221,6 +221,9 @@ void if_register_receive (info)
 		lpf_tr_filter_setup (info);
 	else
 #endif
+	if (info -> hw_address.hbuf [0] == HTYPE_PUREIP)
+		lpf_pureip_filter_setup (info);
+	else
 		lpf_gen_filter_setup (info);
 
 	if (!quiet_interface_discovery)
@@ -255,50 +258,78 @@ void if_deregister_receive (info)
 static void lpf_gen_filter_setup (info)
 	struct interface_info *info;
 {
-	int pure_ip = info -> hw_address.hbuf [0] == HTYPE_PUREIP;
 	struct sock_fprog p;
 
 	memset(&p, 0, sizeof(p));
 
-	/* Set up the bpf filter program structure and patch port(s).
-	 *
-	 * This is defined in bpf.c, XXX changes to filter program may
-	 * require changes to the insn number(s) used below! XXX
-	 */
+	/* Set up the bpf filter program structure.    This is defined in
+	   bpf.c */
+	p.len = dhcp_bpf_filter_len;
+	p.filter = dhcp_bpf_filter;
+
+	dhcp_bpf_filter [8].k = ntohs (local_port);
 
-	if (pure_ip) {
-		p.len = dhcp_bpf_pureip_filter_len;
-		p.filter = dhcp_bpf_pureip_filter;
+        /* Patch the server port into the LPF  program...
+	   XXX changes to filter program may require changes
+	   to the insn number(s) used below! XXX */
+#if defined(RELAY_PORT)
+	if (relay_port) {
+		/*
+		 * If user defined relay UDP port, we need to filter
+		 * also on the user UDP port.
+		 */
+		p.len = dhcp_bpf_relay_filter_len;
+		p.filter = dhcp_bpf_relay_filter;
 
-		/* patch port */
-		dhcp_bpf_pureip_filter [6].k = ntohs (local_port);
-	} else {
-		p.len = dhcp_bpf_filter_len;
-		p.filter = dhcp_bpf_filter;
+		dhcp_bpf_relay_filter [8].k = ntohs (local_port);
+		dhcp_bpf_relay_filter [10].k = ntohs (relay_port);
+	}
+#endif
 
-		/* patch port */
-		dhcp_bpf_filter [8].k = ntohs (local_port);
+	if (setsockopt (info -> rfdesc, SOL_SOCKET, SO_ATTACH_FILTER, &p,
+			sizeof p) < 0) {
+		if (errno == ENOPROTOOPT || errno == EPROTONOSUPPORT ||
+		    errno == ESOCKTNOSUPPORT || errno == EPFNOSUPPORT ||
+		    errno == EAFNOSUPPORT) {
+			log_error ("socket: %m - make sure");
+			log_error ("CONFIG_PACKET (Packet socket) %s",
+				   "and CONFIG_FILTER");
+			log_error ("(Socket Filtering) are enabled %s",
+				   "in your kernel");
+			log_fatal ("configuration!");
+		}
+		log_fatal ("Can't install packet filter program: %m");
 	}
+}
+
+static void lpf_pureip_gen_filter_setup (info)
+	struct interface_info *info;
+{
+	struct sock_fprog p;
+
+	memset(&p, 0, sizeof(p));
+
+	/* Set up the bpf filter program structure.    This is defined in
+	   bpf.c */
+	p.len = dhcp_bpf_pureip_filter_len;
+	p.filter = dhcp_bpf_pureip_filter;
+
+	dhcp_bpf_pureip_filter [6].k = ntohs (local_port);
 
+        /* Patch the server port into the LPF  program...
+	   XXX changes to filter program may require changes
+	   to the insn number(s) used below! XXX */
 #if defined(RELAY_PORT)
-	/*
-	 * If user defined relay UDP port, we need to filter
-	 * also on the user UDP port.
-	 */
-	if (relay_port && pure_ip) {
+	if (relay_port) {
+		/*
+		 * If user defined relay UDP port, we need to filter
+		 * also on the user UDP port.
+		 */
 		p.len = dhcp_bpf_pureip_relay_filter_len;
 		p.filter = dhcp_bpf_pureip_relay_filter;
 
-		/* patch ports */
 		dhcp_bpf_pureip_relay_filter [6].k = ntohs (local_port);
 		dhcp_bpf_pureip_relay_filter [8].k = ntohs (relay_port);
-	} else if (relay_port) {
-		p.len = dhcp_bpf_relay_filter_len;
-		p.filter = dhcp_bpf_relay_filter;
-
-		/* patch ports */
-		dhcp_bpf_relay_filter [8].k = ntohs (local_port);
-		dhcp_bpf_relay_filter [10].k = ntohs (relay_port);
 	}
 #endif
 
-- 
2.39.2


From 58e0d3317795987b2f1ca788645196d0e3543f88 Mon Sep 17 00:00:00 2001
From: Adam Smith <[email protected]>
Date: Tue, 23 Jan 2024 21:47:00 -0500
Subject: [PATCH 3/4] fix compilation errors

---
 common/lpf.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/common/lpf.c b/common/lpf.c
index 0497309f..0114a57e 100644
--- a/common/lpf.c
+++ b/common/lpf.c
@@ -195,6 +195,7 @@ static void lpf_tr_filter_setup (struct interface_info *);
 #endif
 
 static void lpf_gen_filter_setup (struct interface_info *);
+static void lpf_pureip_gen_filter_setup (struct interface_info *);
 
 void if_register_receive (info)
 	struct interface_info *info;
@@ -215,14 +216,13 @@ void if_register_receive (info)
 	}
 #endif
 
-
 #if defined (HAVE_TR_SUPPORT)
 	if (info -> hw_address.hbuf [0] == HTYPE_IEEE802)
 		lpf_tr_filter_setup (info);
 	else
 #endif
 	if (info -> hw_address.hbuf [0] == HTYPE_PUREIP)
-		lpf_pureip_filter_setup (info);
+		lpf_pureip_gen_filter_setup (info);
 	else
 		lpf_gen_filter_setup (info);
 
@@ -349,6 +349,7 @@ static void lpf_pureip_gen_filter_setup (info)
 	}
 }
 
+
 #if defined (HAVE_TR_SUPPORT)
 static void lpf_tr_filter_setup (info)
 	struct interface_info *info;
-- 
2.39.2


From fd96a11b31cd05aae450ec65fde0b5c6e0b718c2 Mon Sep 17 00:00:00 2001
From: Adam Smith <[email protected]>
Date: Tue, 23 Jan 2024 22:35:54 -0500
Subject: [PATCH 4/4] add support for ARPHRD_NONE interface type

---
 common/lpf.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/common/lpf.c b/common/lpf.c
index 0114a57e..52857274 100644
--- a/common/lpf.c
+++ b/common/lpf.c
@@ -627,6 +627,12 @@ get_hw_addr(const char *name, struct hardware *hw) {
 			hw->hlen = 1;
 			hw->hbuf[0] = HTYPE_PUREIP;
 			break;
+#endif
+#ifdef ARPHRD_NONE
+		case ARPHRD_NONE:
+			hw->hlen = 1;
+			hw->hbuf[0] = HTYPE_PUREIP;
+			break;
 #endif
 		default:
 			log_fatal("Unsupported device type %ld for \"%s\"",
-- 
2.39.2

Seemingly long term solution is to migrate to a DHCP/DHCPv6 server that supports it. I've been going back and forth with the systemd folks -- https://github.com/systemd/systemd/issues/27219
One of them seems to show it works fine, but I can't replicate it. I am going to try and do it under just normal Debian 12 bookwork and not VyOS so as not to introduce anything strange

New data point. I installed vanilla Debian 12, connected modem via

qmicli -p -d /dev/cdc-wdm0 --device-open-net='net-raw-ip|net-no-qos-header' --wds-start-network="apn='h2g2',ip-type=4" --client-no-release-cid

Then configured wwan0 with

[Match]
Name=wwan0

[Network]
DHCP=yes
LLDP=no

I was able to get a IPv4 address via DHCP using systemd-networkd no problem. So I believe that's a viable solution if VyOS moves towards using systemd-networkd in the future.

For now, working within what we have already, I think a patched version of dhcp with dhclient that can do raw-ip would be a solution, but it's not fun to run a fork of an EOL project.

Moving to systemd-networkd on the long run seems like a very nice idea that I also had. For no and the 1.4 LTS release we will stick with a custom isc-dhcp-client package build

https://github.com/vyos/vyos-build/pull/503

c-po changed the task status from Open to Needs testing.Feb 15 2024, 9:56 PM
c-po moved this task from Need Triage to Finished on the VyOS 1.4 Sagitta (1.4.0-epa1) board.