Netdev List
 help / color / mirror / Atom feed
* Re: 答复: [外部邮件] Re: [PATCH] net: ipv6: fix a potential use-after-free in ip4ip6_err
From: Ido Schimmel @ 2026-07-19  6:37 UTC (permalink / raw)
  To: Li,Rongqing
  Cc: Xin Long, David Ahern, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, netdev@vger.kernel.org,
	linux-kernel@vger.kernel.org
In-Reply-To: <098518d8f02746a0abeec70110dcb5b8@baidu.com>

On Sat, Jul 18, 2026 at 12:43:48AM +0000, Li,Rongqing wrote:
> 
> 
> > -----邮件原件-----
> > 发件人: Xin Long <lucien.xin@gmail.com>
> > 发送时间: 2026年7月17日 23:53
> > 收件人: Li,Rongqing <lirongqing@baidu.com>
> > 抄送: David Ahern <dsahern@kernel.org>; Ido Schimmel
> > <idosch@nvidia.com>; David S . Miller <davem@davemloft.net>; Eric
> > Dumazet <edumazet@google.com>; Jakub Kicinski <kuba@kernel.org>; Paolo
> > Abeni <pabeni@redhat.com>; Simon Horman <horms@kernel.org>;
> > netdev@vger.kernel.org; linux-kernel@vger.kernel.org
> > 主题: [外部邮件] Re: [PATCH] net: ipv6: fix a potential use-after-free in
> > ip4ip6_err
> > 
> > On Fri, Jul 17, 2026 at 10:33 AM lirongqing <lirongqing@baidu.com> wrote:
> > >
> > > From: Li RongQing <lirongqing@baidu.com>
> > >
> > > Fix a use-after-free bug in ip4ip6_err() where rt->rt_flags is
> > > accessed after the route entry object has been released via ip_rt_put(rt).
> > >
> > > If ip_rt_put() decrements the reference count to zero and frees the
> > > rtable structure, reading rt->rt_flags immediately afterward results
> > > in a use-after-free pointer dereference.
> > >
> > > Fix this by caching rt->rt_flags into a local variable before calling
> > > ip_rt_put().
> > >
> > > Fixes: 77552cfa39c4 ("ip6_tunnel: clean up ip4ip6 and ip6ip6's
> > > err_handlers")
> > > Signed-off-by: Li RongQing <lirongqing@baidu.com>
> > > ---
> > >  net/ipv6/ip6_tunnel.c | 4 +++-
> > >  1 file changed, 3 insertions(+), 1 deletion(-)
> > >
> > > diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c index
> > > bf8e40a..984cb0c 100644
> > > --- a/net/ipv6/ip6_tunnel.c
> > > +++ b/net/ipv6/ip6_tunnel.c
> > > @@ -569,6 +569,7 @@ ip4ip6_err(struct sk_buff *skb, struct
> > > inet6_skb_parm *opt,  {
> > >         __u32 rel_info = ntohl(info);
> > >         const struct iphdr *eiph;
> > > +       unsigned int rt_flags;
> > >         struct sk_buff *skb2;
> > >         int err, rel_msg = 0;
> > >         u8 rel_type = type;
> > > @@ -627,10 +628,11 @@ ip4ip6_err(struct sk_buff *skb, struct
> > inet6_skb_parm *opt,
> > >                 goto out;
> > >
> > >         skb2->dev = rt->dst.dev;
> > > +       rt_flags = rt->rt_flags;
> > >         ip_rt_put(rt);
> > >
> > >         /* route "incoming" packet */
> > > -       if (rt->rt_flags & RTCF_LOCAL) {
> > > +       if (rt_flags & RTCF_LOCAL) {
> > >                 rt = ip_route_output_ports(dev_net(skb->dev), &fl4,
> > NULL,
> > >                                            eiph->daddr, eiph->saddr,
> > 0, 0,
> > >                                            IPPROTO_IPIP,
> > > --
> > > 2.9.4
> > >
> > Have you already seen any problem triggered by this?
> > 
> > I don't really think there's a use-after-free issue here.
> > 
> > The entire IPv6 input path, including ICMPv6 error handling, runs under
> > rcu_read_lock(). Since dst_release() uses call_rcu_hurry() to defer the actual
> > freeing until after the RCU grace period, accessing rt->rt_flags after
> > ip_rt_put(rt) is completely safe in this context.
> > 
> 
> Thanks for the review. However, The issue is that after ip_rt_put(rt), the rt pointer may be logically freed if refcnt drops to zero, and dereferencing it afterwards is illegal even if memory isn't recycled immediately. The patch avoids this by caching rt->rt_flags before the put operation. This is a defensive fix against potential UAF reported by static analyzers. 

I agree with Xin. I don't see how a UAF can happen here. If you are
claiming to fix a use-after-free, then reproduce it and put a relevant
trace in the commit message.

^ permalink raw reply

* Re: Question: MII media mux for external PHY/SFP combo ports
From: Maxime Chevallier @ 2026-07-19  6:10 UTC (permalink / raw)
  To: git; +Cc: andrew, netdev, olteanv
In-Reply-To: <178442713834.7.10284519099797443296.1492041940@elinell1337.me>

Hi Pavel,

On 7/19/26 04:12, git@elinell1337.me wrote:
> Hi Maxime,
> 
> Sorry for the duplicate email. The previous copy was rejected by the list because it contained HTML (default mode in Proton Mail).
> This is a plain-text resend.
> 
> I am working on upstream support for the Banana Pi BPI-R4 Pro 8X and
> would like guidance before writing any board-specific mux code.

It's still an ongoing topic for me, I'm iterating on the prerequisite for
proper MUX support that is phy_port (i.e. expose the ports of the mux
to users), the next series will be about muxes :) Depending on when the
current phy_port gets accepted, I expect that mux support will start being
upstreamed in a few weeks from now, I'll make sure to CC you.

In terms of testing, I have :
 - Combo-ports where both SFP and RJ45 are connected to the same PHY
 - Combo-ports behind a DSA switch (a Marvell 6190x)
 - Combo-ports with a mii MUX driven by gpio (turris omnia)
 - Dual-port RJ45 with 2 PHYs

> 
> The board [1] has two independent RJ45/SFP combo connectors. Each has
> one SerDes lane, a GPIO-controlled passive external mux, one AS21010P
> copper PHY endpoint, and one SFP cage endpoint. One mux is connected
> to an MT7988 MAC. The other is connected to an MxL86252 DSA user port.
> 
> The downstream policy selects SFP when a module is present and copper
> when the cage is empty. Each combo must keep one logical network port,
> its MAC address, and its bridge/VLAN configuration across a media
> transition. Switching either combo must not affect the other combo or
> the remaining switch ports.
> 
> I have downstream reference code [3,4], but it polls MOD_DEF0 and
> replaces driver-private MediaTek or DSA phylink state. I am treating it
> only as bring-up evidence.
> 
> The phy_port documentation [2] added by commit 62518b5b3d8e describes
> external MII mux topologies and identifies an MII mux framework as
> future work.
> 
> Does runtime MII/media mux follow-up work already exist or is it
> planned? If not, is phy_port the right starting point for a generic
> design that covers both MAC and DSA consumers?

Yes it'll cover both, the new DT binding will be generic, and the SW
support for MUX registration will be done directly by phylink. What
will be needed it the logic to drive the MUX

> 
> I can provide the exact DTS topology, downstream references, and a board
> for copper/SFP hot-plug, traffic, and concurrent-combo testing.
I'll CC you for the next iterations, what really helps if you need that
going forward is review and testing of the code.

Next revision will go out next week, and hopefully I can start sending
the mii mux work soon after :)

Maxime

^ permalink raw reply

* Re: [PATCH net-next v2 1/2] dt-bindings: net: Add Maxio MAE0621A PHY
From: Krzysztof Kozlowski @ 2026-07-19  6:04 UTC (permalink / raw)
  To: Liu Changjie, Andrew Lunn, Heiner Kallweit, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, netdev
  Cc: Russell King, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Luo Jie, Wolfram Sang, devicetree, linux-kernel
In-Reply-To: <MN0PR19MB6091F865EF7FE6AC1EE41C10ACC62@MN0PR19MB6091.namprd19.prod.outlook.com>

On 17/07/2026 05:44, Liu Changjie wrote:
> Document the MAE0621A PHY ID and its optional 125 MHz CLKOUT
> setting. Boards which do not provide the property retain the hardware
> strap or bootloader configuration.
> 
> Only the 125 MHz setting is exposed because it is the only setting
> exercised on hardware.

Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>

Best regards,
Krzysztof

^ permalink raw reply

* [PATCH net-next v3 3/3] net: dsa: mv88e6xxx: apply embedded PTP arrival times inline
From: Luke Howard @ 2026-07-19  5:30 UTC (permalink / raw)
  To: Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Vivien Didelot, Gregory CLEMENT, Andrew Lunn,
	Richard Cochran
  Cc: Cedric Jehasse, Kieran Tyrrell, Max Holtmann, Max Hunter,
	Christoph Mellauner, Simon Gapp, netdev, linux-kernel,
	Luke Howard
In-Reply-To: <20260719-mv88e6xxx-ptp-fixes-v3-0-161386ac8a62@padl.com>

Embedded PTP arrival times can be extracted without using the PTP
worker, avoiding the issue where PTP general messages could arrive
before the timestamped event messages. DSA can deliver the frame
normally, similar to ocelot_ptp_rx_timestamp().

The register-based arrival path is unchanged.

Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Luke Howard <lukeh@padl.com>
---
 drivers/net/dsa/mv88e6xxx/chip.c     |  1 +
 drivers/net/dsa/mv88e6xxx/chip.h     |  7 ++--
 drivers/net/dsa/mv88e6xxx/hwtstamp.c | 69 +++++++++++++-----------------------
 drivers/net/dsa/mv88e6xxx/ptp.c      | 65 ++++++++++++++++++++++++++++-----
 drivers/net/dsa/mv88e6xxx/ptp.h      |  1 +
 5 files changed, 87 insertions(+), 56 deletions(-)

diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index c3277c1f3d785..624cdf6c80a67 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -6638,6 +6638,7 @@ static struct mv88e6xxx_chip *mv88e6xxx_alloc_chip(struct device *dev)
 	chip->dev = dev;
 
 	mutex_init(&chip->reg_lock);
+	spin_lock_init(&chip->ptp_clock_lock);
 	INIT_LIST_HEAD(&chip->mdios);
 	idr_init(&chip->policies);
 	INIT_LIST_HEAD(&chip->msts);
diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h
index b6a90eba81c43..dd52de71360b9 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.h
+++ b/drivers/net/dsa/mv88e6xxx/chip.h
@@ -427,9 +427,12 @@ struct mv88e6xxx_chip {
 	/* GPIO resources */
 	u8 gpio_data[2];
 
-	/* This cyclecounter abstracts the switch PTP time.
-	 * reg_lock must be held for any operation that read()s.
+	/* This cyclecounter abstracts the switch PTP time. ptp_clock_lock
+	 * protects tstamp_cc and tstamp_tc. tstamp_cycles caches the
+	 * result most recently returned by mv88e6xxx_ptp_read_cycles().
 	 */
+	spinlock_t		ptp_clock_lock;
+	u64			tstamp_cycles;
 	struct cyclecounter	tstamp_cc;
 	struct timecounter	tstamp_tc;
 	struct delayed_work	overflow_work;
diff --git a/drivers/net/dsa/mv88e6xxx/hwtstamp.c b/drivers/net/dsa/mv88e6xxx/hwtstamp.c
index fa5e897182ca2..716cbc2a86922 100644
--- a/drivers/net/dsa/mv88e6xxx/hwtstamp.c
+++ b/drivers/net/dsa/mv88e6xxx/hwtstamp.c
@@ -283,35 +283,24 @@ static bool parse_embedded_ts(unsigned int arr_ts_mode,
 	return true;
 }
 
-static void mv88e6xxx_get_rxts_embedded(struct mv88e6xxx_chip *chip,
-					struct mv88e6xxx_port_hwtstamp *ps,
-					struct sk_buff *skb)
+/* Apply the arrival time the switch embedded in the frame. No register access
+ * is needed, so this runs inline on the receive path rather than being handed
+ * to the PTP worker.
+ */
+static void mv88e6xxx_ptp_rx_timestamp(struct mv88e6xxx_chip *chip,
+				       struct sk_buff *skb)
 {
-	struct sk_buff_head *rxq = &ps->rx_queue;
 	struct skb_shared_hwtstamps *shwt;
-	struct sk_buff_head received;
-	unsigned long flags;
 	u64 ns;
 
-	__skb_queue_head_init(&received);
-	__skb_queue_head(&received, skb);
-	spin_lock_irqsave(&rxq->lock, flags);
-	skb_queue_splice_tail_init(rxq, &received);
-	spin_unlock_irqrestore(&rxq->lock, flags);
+	if (!parse_embedded_ts(chip->info->arr_ts_mode, skb, &ns))
+		return;
 
-	mv88e6xxx_reg_lock(chip);
-	skb_queue_walk(&received, skb) {
-		if (!parse_embedded_ts(chip->info->arr_ts_mode, skb, &ns))
-			continue;
-		ns = timecounter_cyc2time(&chip->tstamp_tc, ns);
-		shwt = skb_hwtstamps(skb);
-		memset(shwt, 0, sizeof(*shwt));
-		shwt->hwtstamp = ns_to_ktime(ns);
-	}
-	mv88e6xxx_reg_unlock(chip);
+	ns = mv88e6xxx_timecounter_cyc2time(chip, ns);
 
-	while ((skb = __skb_dequeue(&received)))
-		netif_rx(skb);
+	shwt = skb_hwtstamps(skb);
+	memset(shwt, 0, sizeof(*shwt));
+	shwt->hwtstamp = ns_to_ktime(ns);
 }
 
 static void mv88e6xxx_get_rxts(struct mv88e6xxx_chip *chip,
@@ -358,9 +347,7 @@ static void mv88e6xxx_get_rxts(struct mv88e6xxx_chip *chip,
 		if (mv88e6xxx_ts_valid(status) && seq_match(skb, seq_id)) {
 			ns = timehi << 16 | timelo;
 
-			mv88e6xxx_reg_lock(chip);
-			ns = timecounter_cyc2time(&chip->tstamp_tc, ns);
-			mv88e6xxx_reg_unlock(chip);
+			ns = mv88e6xxx_timecounter_cyc2time(chip, ns);
 			shwt = skb_hwtstamps(skb);
 			memset(shwt, 0, sizeof(*shwt));
 			shwt->hwtstamp = ns_to_ktime(ns);
@@ -376,20 +363,6 @@ static void mv88e6xxx_rxtstamp_work(struct mv88e6xxx_chip *chip,
 	const struct mv88e6xxx_ptp_ops *ptp_ops = chip->info->ops->ptp_ops;
 	struct sk_buff *skb;
 
-	if (chip->info->arr_ts_mode) {
-		/* If arr_ts_mode is set, the timestamps are embedded in the
-		 * frames so a register read is not required. We still need a
-		 * work queue rather than processing inline because
-		 * timecounter_cyc2time() takes the global mutex and this
-		 * cannot be called from mv88e6xxx_port_rxtstamp().
-		 */
-		skb = skb_dequeue(&ps->rx_queue);
-		if (skb)
-			mv88e6xxx_get_rxts_embedded(chip, ps, skb);
-
-		return;
-	}
-
 	skb = skb_dequeue(&ps->rx_queue);
 	if (skb)
 		mv88e6xxx_get_rxts(chip, ps, skb, ptp_ops->arr0_sts_reg,
@@ -432,7 +405,13 @@ bool mv88e6xxx_port_rxtstamp(struct dsa_switch *ds, int port,
 
 	SKB_PTP_TYPE(skb) = type;
 
-	if (!chip->info->arr_ts_mode && is_pdelay_msg(hdr))
+	/* Embedded arrival times can be returned inline. */
+	if (chip->info->arr_ts_mode) {
+		mv88e6xxx_ptp_rx_timestamp(chip, skb);
+		return false;
+	}
+
+	if (is_pdelay_msg(hdr))
 		skb_queue_tail(&ps->rx_queue2, skb);
 	else
 		skb_queue_tail(&ps->rx_queue, skb);
@@ -498,9 +477,7 @@ static int mv88e6xxx_txtstamp_work(struct mv88e6xxx_chip *chip,
 
 	memset(&shhwtstamps, 0, sizeof(shhwtstamps));
 	time_raw = ((u32)departure_block[2] << 16) | departure_block[1];
-	mv88e6xxx_reg_lock(chip);
-	ns = timecounter_cyc2time(&chip->tstamp_tc, time_raw);
-	mv88e6xxx_reg_unlock(chip);
+	ns = mv88e6xxx_timecounter_cyc2time(chip, time_raw);
 	shhwtstamps.hwtstamp = ns_to_ktime(ns);
 
 	dev_dbg(chip->dev,
@@ -543,7 +520,9 @@ long mv88e6xxx_hwtstamp_work(struct ptp_clock_info *ptp)
 		if (test_bit(MV88E6XXX_HWTSTAMP_TX_IN_PROGRESS, &ps->state))
 			restart |= mv88e6xxx_txtstamp_work(chip, ps);
 
-		mv88e6xxx_rxtstamp_work(chip, ps);
+		/* Embedded arrival times are applied on the receive path. */
+		if (!chip->info->arr_ts_mode)
+			mv88e6xxx_rxtstamp_work(chip, ps);
 	}
 
 	return restart ? 1 : -1;
diff --git a/drivers/net/dsa/mv88e6xxx/ptp.c b/drivers/net/dsa/mv88e6xxx/ptp.c
index f7603573d3a98..2a7f97625e375 100644
--- a/drivers/net/dsa/mv88e6xxx/ptp.c
+++ b/drivers/net/dsa/mv88e6xxx/ptp.c
@@ -231,15 +231,37 @@ static void mv88e6352_tai_event_work(struct work_struct *ugly)
 
 	/* We only have one timestamping channel. */
 	ev.index = 0;
-	mv88e6xxx_reg_lock(chip);
-	ev.timestamp = timecounter_cyc2time(&chip->tstamp_tc, raw_ts);
-	mv88e6xxx_reg_unlock(chip);
+	ev.timestamp = mv88e6xxx_timecounter_cyc2time(chip, raw_ts);
 
 	ptp_clock_event(chip->ptp_clock, &ev);
 out:
 	schedule_delayed_work(&chip->tai_event_work, TAI_EVENT_WORK_INTERVAL);
 }
 
+/* Refresh the cached counter value that the read() callback returns. The
+ * read cannot acquire the register lock whilst holding ptp_clock_lock
+ * because MDIO reads can sleep. The caller must hold reg_lock, which
+ * serializes against the other timecounter writers.
+ */
+static void mv88e6xxx_ptp_read_cycles(struct mv88e6xxx_chip *chip)
+{
+	const struct mv88e6xxx_ptp_ops *ptp_ops = chip->info->ops->ptp_ops;
+
+	if (ptp_ops->clock_read)
+		chip->tstamp_cycles = ptp_ops->clock_read(&chip->tstamp_cc);
+}
+
+u64 mv88e6xxx_timecounter_cyc2time(struct mv88e6xxx_chip *chip, u64 cycles)
+{
+	u64 ns;
+
+	spin_lock_bh(&chip->ptp_clock_lock);
+	ns = timecounter_cyc2time(&chip->tstamp_tc, cycles);
+	spin_unlock_bh(&chip->ptp_clock_lock);
+
+	return ns;
+}
+
 static int mv88e6xxx_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
 {
 	struct mv88e6xxx_chip *chip = ptp_to_chip(ptp);
@@ -258,9 +280,12 @@ static int mv88e6xxx_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
 	diff = div_u64(adj, chip->cc_coeffs->cc_mult_dem);
 
 	mv88e6xxx_reg_lock(chip);
+	mv88e6xxx_ptp_read_cycles(chip);
 
+	spin_lock_bh(&chip->ptp_clock_lock);
 	timecounter_read(&chip->tstamp_tc);
 	chip->tstamp_cc.mult = neg_adj ? mult - diff : mult + diff;
+	spin_unlock_bh(&chip->ptp_clock_lock);
 
 	mv88e6xxx_reg_unlock(chip);
 
@@ -271,8 +296,16 @@ static int mv88e6xxx_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
 {
 	struct mv88e6xxx_chip *chip = ptp_to_chip(ptp);
 
+	/* No register access is needed here, but reg_lock still serialises
+	 * this against the other timecounter writers, which drop it only
+	 * after their hardware read has completed.
+	 */
 	mv88e6xxx_reg_lock(chip);
+
+	spin_lock_bh(&chip->ptp_clock_lock);
 	timecounter_adjtime(&chip->tstamp_tc, delta);
+	spin_unlock_bh(&chip->ptp_clock_lock);
+
 	mv88e6xxx_reg_unlock(chip);
 
 	return 0;
@@ -285,7 +318,12 @@ static int mv88e6xxx_ptp_gettime(struct ptp_clock_info *ptp,
 	u64 ns;
 
 	mv88e6xxx_reg_lock(chip);
+	mv88e6xxx_ptp_read_cycles(chip);
+
+	spin_lock_bh(&chip->ptp_clock_lock);
 	ns = timecounter_read(&chip->tstamp_tc);
+	spin_unlock_bh(&chip->ptp_clock_lock);
+
 	mv88e6xxx_reg_unlock(chip);
 
 	*ts = ns_to_timespec64(ns);
@@ -302,7 +340,12 @@ static int mv88e6xxx_ptp_settime(struct ptp_clock_info *ptp,
 	ns = timespec64_to_ns(ts);
 
 	mv88e6xxx_reg_lock(chip);
+	mv88e6xxx_ptp_read_cycles(chip);
+
+	spin_lock_bh(&chip->ptp_clock_lock);
 	timecounter_init(&chip->tstamp_tc, &chip->tstamp_cc, ns);
+	spin_unlock_bh(&chip->ptp_clock_lock);
+
 	mv88e6xxx_reg_unlock(chip);
 
 	return 0;
@@ -444,14 +487,12 @@ const struct mv88e6xxx_ptp_ops mv88e6390_ptp_ops = {
 		(1 << HWTSTAMP_FILTER_PTP_V2_DELAY_REQ),
 };
 
+/* Return the value most recently fetched by mv88e6xxx_ptp_read_cycles()
+ * rather than reading the hardware over MDIO.
+ */
 static u64 mv88e6xxx_ptp_clock_read(struct cyclecounter *cc)
 {
-	struct mv88e6xxx_chip *chip = cc_to_chip(cc);
-
-	if (chip->info->ops->ptp_ops->clock_read)
-		return chip->info->ops->ptp_ops->clock_read(cc);
-
-	return 0;
+	return cc_to_chip(cc)->tstamp_cycles;
 }
 
 /* With a 250MHz input clock, the 32-bit timestamp counter overflows in ~17.2
@@ -486,6 +527,12 @@ int mv88e6xxx_ptp_setup(struct mv88e6xxx_chip *chip)
 	chip->tstamp_cc.mult	= chip->cc_coeffs->cc_mult;
 	chip->tstamp_cc.shift	= chip->cc_coeffs->cc_shift;
 
+	/* Prime the cycle counter cache for the timecounter_init() below.
+	 * The caller holds reg_lock, and nothing can reach the PTP clock
+	 * until ptp_clock_register() below, so no locking is needed here.
+	 */
+	mv88e6xxx_ptp_read_cycles(chip);
+
 	timecounter_init(&chip->tstamp_tc, &chip->tstamp_cc,
 			 ktime_to_ns(ktime_get_real()));
 
diff --git a/drivers/net/dsa/mv88e6xxx/ptp.h b/drivers/net/dsa/mv88e6xxx/ptp.h
index 95bdddb0bf39f..44718e120de20 100644
--- a/drivers/net/dsa/mv88e6xxx/ptp.h
+++ b/drivers/net/dsa/mv88e6xxx/ptp.h
@@ -68,6 +68,7 @@
 
 int mv88e6xxx_ptp_setup(struct mv88e6xxx_chip *chip);
 void mv88e6xxx_ptp_free(struct mv88e6xxx_chip *chip);
+u64 mv88e6xxx_timecounter_cyc2time(struct mv88e6xxx_chip *chip, u64 cycles);
 
 #define ptp_to_chip(ptp) container_of(ptp, struct mv88e6xxx_chip,	\
 				      ptp_clock_info)

-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next v3 1/3] net: dsa: mv88e6xxx: use ARRIVAL1 counter for all peer delay messages
From: Luke Howard @ 2026-07-19  5:30 UTC (permalink / raw)
  To: Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Vivien Didelot, Gregory CLEMENT, Andrew Lunn,
	Richard Cochran
  Cc: Cedric Jehasse, Kieran Tyrrell, Max Holtmann, Max Hunter,
	Christoph Mellauner, Simon Gapp, netdev, linux-kernel,
	Luke Howard
In-Reply-To: <20260719-mv88e6xxx-ptp-fixes-v3-0-161386ac8a62@padl.com>

mv88e6xxx switches have two arrival timestamp counters for timestamping
PTP event messages. This permits more than one arriving event message's
timestamp to be captured. This is useful for the case where event
messages from a grandmaster arrive at the same time as PDelayReq/PDelayResp
messages from a peer.

Previously only PDelayResp messages were assigned to the second arrival
counter; this patch does so for PDelayReq messages as well.

Signed-off-by: Luke Howard <lukeh@padl.com>
---
 drivers/net/dsa/mv88e6xxx/hwtstamp.c | 16 ++++++++++++----
 1 file changed, 12 insertions(+), 4 deletions(-)

diff --git a/drivers/net/dsa/mv88e6xxx/hwtstamp.c b/drivers/net/dsa/mv88e6xxx/hwtstamp.c
index 6e6472a3b75ad..57ff77496864f 100644
--- a/drivers/net/dsa/mv88e6xxx/hwtstamp.c
+++ b/drivers/net/dsa/mv88e6xxx/hwtstamp.c
@@ -319,9 +319,16 @@ static void mv88e6xxx_rxtstamp_work(struct mv88e6xxx_chip *chip,
 				   &ps->rx_queue2);
 }
 
-static int is_pdelay_resp(const struct ptp_header *hdr)
+static bool is_pdelay_msg(const struct ptp_header *hdr)
 {
-	return (hdr->tsmt & 0xf) == 3;
+	switch (ptp_get_msgtype(hdr, PTP_CLASS_V2)) {
+	case PTP_MSGTYPE_PDELAY_REQ:
+		fallthrough;
+	case PTP_MSGTYPE_PDELAY_RESP:
+		return true;
+	default:
+		return false;
+	}
 }
 
 bool mv88e6xxx_port_rxtstamp(struct dsa_switch *ds, int port,
@@ -343,7 +350,7 @@ bool mv88e6xxx_port_rxtstamp(struct dsa_switch *ds, int port,
 
 	SKB_PTP_TYPE(skb) = type;
 
-	if (is_pdelay_resp(hdr))
+	if (is_pdelay_msg(hdr))
 		skb_queue_tail(&ps->rx_queue2, skb);
 	else
 		skb_queue_tail(&ps->rx_queue, skb);
@@ -584,8 +591,9 @@ int mv88e6xxx_hwtstamp_setup(struct mv88e6xxx_chip *chip)
 	if (err)
 		return err;
 
-	/* Use ARRIVAL1 for peer delay response messages. */
+	/* Use ARRIVAL1 for peer delay messages. */
 	err = mv88e6xxx_ptp_write(chip, MV88E6XXX_PTP_TS_ARRIVAL_PTR,
+				  MV88E6XXX_PTP_MSGTYPE_PDLAY_REQ |
 				  MV88E6XXX_PTP_MSGTYPE_PDLAY_RES);
 	if (err)
 		return err;

-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next v3 2/3] net: dsa: mv88e6xxx: embedded PTP timestamp support
From: Luke Howard @ 2026-07-19  5:30 UTC (permalink / raw)
  To: Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Vivien Didelot, Gregory CLEMENT, Andrew Lunn,
	Richard Cochran
  Cc: Cedric Jehasse, Kieran Tyrrell, Max Holtmann, Max Hunter,
	Christoph Mellauner, Simon Gapp, netdev, linux-kernel,
	Luke Howard
In-Reply-To: <20260719-mv88e6xxx-ptp-fixes-v3-0-161386ac8a62@padl.com>

mv88e6xxx switches can support embedding PTP timestamps directly
in the frame, either as a trailer or at a configurable offset
(typically the reserved bytes in the PTP header).

Add support for this on the 88E6341 and 88E6352 switches, being
those on which I was able to verify this. Other switch chips may
also work. The arrival timestamp offsets are relative to the PTP
common header and will work for both L2 and L3 PTP packets; the
respective headers are skipped by the number of bytes set in the
ETJump and IPJump registers, which are initialized to sensible
defaults and are VLAN tag-aware.

(Note: the 6352 datasheet incorrectly states that ETJump and
IPJump are initialized to zero. They are initialized to 12 and
2 respectively; this is corrected in the 6341 data sheet.)

Signed-off-by: Luke Howard <lukeh@padl.com>
---
 drivers/net/dsa/mv88e6xxx/chip.c     |   3 +
 drivers/net/dsa/mv88e6xxx/chip.h     |   8 +++
 drivers/net/dsa/mv88e6xxx/hwtstamp.c | 131 +++++++++++++++++++++++++++++++----
 drivers/net/dsa/mv88e6xxx/hwtstamp.h |  14 ++++
 4 files changed, 144 insertions(+), 12 deletions(-)

diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 80b877c74513d..c3277c1f3d785 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -28,6 +28,7 @@
 #include <linux/of_mdio.h>
 #include <linux/platform_data/mv88e6xxx.h>
 #include <linux/property.h>
+#include <linux/ptp_classify.h>
 #include <linux/netdevice.h>
 #include <linux/gpio/consumer.h>
 #include <linux/phylink.h>
@@ -6370,6 +6371,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.edsa_support = MV88E6XXX_EDSA_SUPPORTED,
 		.ptp_support = true,
 		.ops = &mv88e6341_ops,
+		.arr_ts_mode = offsetof(struct ptp_header, reserved2),
 	},
 
 	[MV88E6350] = {
@@ -6447,6 +6449,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.edsa_support = MV88E6XXX_EDSA_SUPPORTED,
 		.ptp_support = true,
 		.ops = &mv88e6352_ops,
+		.arr_ts_mode = offsetof(struct ptp_header, reserved2),
 	},
 	[MV88E6361] = {
 		.prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6361,
diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h
index e966e7c4cc5de..b6a90eba81c43 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.h
+++ b/drivers/net/dsa/mv88e6xxx/chip.h
@@ -180,6 +180,14 @@ struct mv88e6xxx_info {
 	 * port 0, 1 means internal PHYs range starts at port 1, etc
 	 */
 	unsigned int internal_phys_offset;
+
+	/* Arrival Time Stamp Mode (ArrTSMode); see the ArrTSMode encoding in
+	 * hwtstamp.h. Zero (the default) leaves arrival time stamps in the
+	 * switch registers; non-zero embeds them in the frame, either appended
+	 * as a trailer or overwritten at that byte offset past the start of the
+	 * PTP common header.
+	 */
+	unsigned int arr_ts_mode;
 };
 
 struct mv88e6xxx_atu_entry {
diff --git a/drivers/net/dsa/mv88e6xxx/hwtstamp.c b/drivers/net/dsa/mv88e6xxx/hwtstamp.c
index 57ff77496864f..fa5e897182ca2 100644
--- a/drivers/net/dsa/mv88e6xxx/hwtstamp.c
+++ b/drivers/net/dsa/mv88e6xxx/hwtstamp.c
@@ -15,6 +15,7 @@
 #include "hwtstamp.h"
 #include "ptp.h"
 #include <linux/ptp_classify.h>
+#include <linux/unaligned.h>
 
 #define SKB_PTP_TYPE(__skb) (*(unsigned int *)((__skb)->cb))
 
@@ -245,6 +246,74 @@ static int seq_match(struct sk_buff *skb, u16 ts_seqid)
 	return ts_seqid == ntohs(hdr->sequence_id);
 }
 
+static bool parse_embedded_ts(unsigned int arr_ts_mode,
+			      struct sk_buff *skb, u64 *ns)
+{
+	struct ptp_header *hdr;
+
+	*ns = 0;
+
+	/* APPEND means the switch appended the time stamp as a 4-byte trailer
+	 * (not all switches support this). Any other non-zero value is the byte
+	 * offset past the start of the PTP common header at which the switch
+	 * overwrote the time stamp in place (e.g. the reserved header bytes).
+	 */
+	if (arr_ts_mode == MV88E6XXX_PTP_ARR_TS_MODE_APPEND && skb->len >= 4) {
+		if (skb_linearize(skb))
+			return false;
+
+		*ns = (u64)get_unaligned_be32(skb_tail_pointer(skb) - 4);
+		if (pskb_trim_rcsum(skb, skb->len - 4))
+			return false;
+	} else if (arr_ts_mode + 4 <= sizeof(*hdr)) {
+		if (skb_linearize(skb))
+			return false;
+
+		hdr = ptp_parse_header(skb, SKB_PTP_TYPE(skb));
+		if (!hdr)
+			return false;
+
+		*ns = (u64)get_unaligned_be32((u8 *)hdr + arr_ts_mode);
+		memset((u8 *)hdr + arr_ts_mode, 0, 4);
+		skb_checksum_complete_unset(skb);
+	} else {
+		return false;
+	}
+
+	return true;
+}
+
+static void mv88e6xxx_get_rxts_embedded(struct mv88e6xxx_chip *chip,
+					struct mv88e6xxx_port_hwtstamp *ps,
+					struct sk_buff *skb)
+{
+	struct sk_buff_head *rxq = &ps->rx_queue;
+	struct skb_shared_hwtstamps *shwt;
+	struct sk_buff_head received;
+	unsigned long flags;
+	u64 ns;
+
+	__skb_queue_head_init(&received);
+	__skb_queue_head(&received, skb);
+	spin_lock_irqsave(&rxq->lock, flags);
+	skb_queue_splice_tail_init(rxq, &received);
+	spin_unlock_irqrestore(&rxq->lock, flags);
+
+	mv88e6xxx_reg_lock(chip);
+	skb_queue_walk(&received, skb) {
+		if (!parse_embedded_ts(chip->info->arr_ts_mode, skb, &ns))
+			continue;
+		ns = timecounter_cyc2time(&chip->tstamp_tc, ns);
+		shwt = skb_hwtstamps(skb);
+		memset(shwt, 0, sizeof(*shwt));
+		shwt->hwtstamp = ns_to_ktime(ns);
+	}
+	mv88e6xxx_reg_unlock(chip);
+
+	while ((skb = __skb_dequeue(&received)))
+		netif_rx(skb);
+}
+
 static void mv88e6xxx_get_rxts(struct mv88e6xxx_chip *chip,
 			       struct mv88e6xxx_port_hwtstamp *ps,
 			       struct sk_buff *skb, u16 reg,
@@ -307,8 +376,21 @@ static void mv88e6xxx_rxtstamp_work(struct mv88e6xxx_chip *chip,
 	const struct mv88e6xxx_ptp_ops *ptp_ops = chip->info->ops->ptp_ops;
 	struct sk_buff *skb;
 
-	skb = skb_dequeue(&ps->rx_queue);
+	if (chip->info->arr_ts_mode) {
+		/* If arr_ts_mode is set, the timestamps are embedded in the
+		 * frames so a register read is not required. We still need a
+		 * work queue rather than processing inline because
+		 * timecounter_cyc2time() takes the global mutex and this
+		 * cannot be called from mv88e6xxx_port_rxtstamp().
+		 */
+		skb = skb_dequeue(&ps->rx_queue);
+		if (skb)
+			mv88e6xxx_get_rxts_embedded(chip, ps, skb);
 
+		return;
+	}
+
+	skb = skb_dequeue(&ps->rx_queue);
 	if (skb)
 		mv88e6xxx_get_rxts(chip, ps, skb, ptp_ops->arr0_sts_reg,
 				   &ps->rx_queue);
@@ -350,7 +432,7 @@ bool mv88e6xxx_port_rxtstamp(struct dsa_switch *ds, int port,
 
 	SKB_PTP_TYPE(skb) = type;
 
-	if (is_pdelay_msg(hdr))
+	if (!chip->info->arr_ts_mode && is_pdelay_msg(hdr))
 		skb_queue_tail(&ps->rx_queue2, skb);
 	else
 		skb_queue_tail(&ps->rx_queue, skb);
@@ -530,14 +612,37 @@ int mv88e6165_global_enable(struct mv88e6xxx_chip *chip)
 
 int mv88e6352_hwtstamp_port_disable(struct mv88e6xxx_chip *chip, int port)
 {
-	return mv88e6xxx_port_ptp_write(chip, port, MV88E6XXX_PORT_PTP_CFG0,
-					MV88E6XXX_PORT_PTP_CFG0_DISABLE_PTP);
+	int err;
+
+	err = mv88e6xxx_port_ptp_write(chip, port, MV88E6XXX_PORT_PTP_CFG0,
+				       MV88E6XXX_PORT_PTP_CFG0_DISABLE_PTP);
+	if (err)
+		return err;
+
+	err = mv88e6xxx_port_ptp_write(chip, port, MV88E6XXX_PORT_PTP_CFG2, 0);
+	if (err)
+		return err;
+
+	return 0;
 }
 
 int mv88e6352_hwtstamp_port_enable(struct mv88e6xxx_chip *chip, int port)
 {
-	return mv88e6xxx_port_ptp_write(chip, port, MV88E6XXX_PORT_PTP_CFG0,
-					MV88E6XXX_PORT_PTP_CFG0_DISABLE_TSPEC_MATCH);
+	int err;
+
+	if (chip->info->arr_ts_mode) {
+		err = mv88e6xxx_port_ptp_write(chip, port, MV88E6XXX_PORT_PTP_CFG2,
+					       chip->info->arr_ts_mode << 8);
+		if (err)
+			return err;
+	}
+
+	err = mv88e6xxx_port_ptp_write(chip, port, MV88E6XXX_PORT_PTP_CFG0,
+				       MV88E6XXX_PORT_PTP_CFG0_DISABLE_TSPEC_MATCH);
+	if (err)
+		return err;
+
+	return 0;
 }
 
 static int mv88e6xxx_hwtstamp_port_setup(struct mv88e6xxx_chip *chip, int port)
@@ -591,12 +696,14 @@ int mv88e6xxx_hwtstamp_setup(struct mv88e6xxx_chip *chip)
 	if (err)
 		return err;
 
-	/* Use ARRIVAL1 for peer delay messages. */
-	err = mv88e6xxx_ptp_write(chip, MV88E6XXX_PTP_TS_ARRIVAL_PTR,
-				  MV88E6XXX_PTP_MSGTYPE_PDLAY_REQ |
-				  MV88E6XXX_PTP_MSGTYPE_PDLAY_RES);
-	if (err)
-		return err;
+	if (!chip->info->arr_ts_mode) {
+		/* Use ARRIVAL1 for peer delay messages. */
+		err = mv88e6xxx_ptp_write(chip, MV88E6XXX_PTP_TS_ARRIVAL_PTR,
+					  MV88E6XXX_PTP_MSGTYPE_PDLAY_REQ |
+					  MV88E6XXX_PTP_MSGTYPE_PDLAY_RES);
+		if (err)
+			return err;
+	}
 
 	/* 88E6341 devices default to timestamping at the PHY, but this has
 	 * a hardware issue that results in unreliable timestamps. Force
diff --git a/drivers/net/dsa/mv88e6xxx/hwtstamp.h b/drivers/net/dsa/mv88e6xxx/hwtstamp.h
index c359821d5a6ea..c25f53923e768 100644
--- a/drivers/net/dsa/mv88e6xxx/hwtstamp.h
+++ b/drivers/net/dsa/mv88e6xxx/hwtstamp.h
@@ -68,6 +68,20 @@
 #define MV88E6XXX_PORT_PTP_CFG2_DEP_IRQ_EN		0x0002
 #define MV88E6XXX_PORT_PTP_CFG2_ARR_IRQ_EN		0x0001
 
+/* Arrival Time Stamp Mode (ArrTSMode), CFG2 bits [15:8]: configures how the
+ * switch embeds the arrival time stamp (PTPArr0Time) into enabled PTP event
+ * frames.
+ *   0x00        frame modification disabled (time stamp read from registers)
+ *   0x01        append the 4-byte time stamp at the end of the frame,
+ *               growing the frame by four bytes
+ *   0x04..0xEF  overwrite the 4-byte time stamp in place, that many bytes past
+ *               the start of the PTP common header, without growing the frame
+ *               (offsetof(struct ptp_header, reserved2) targets the reserved
+ *               bytes of the header)
+ *   others      reserved
+ */
+#define MV88E6XXX_PTP_ARR_TS_MODE_APPEND		0x01
+
 /* Offset 0x03: PTP LED Configuration */
 #define MV88E6XXX_PORT_PTP_LED_CFG	0x03
 

-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next v3 0/3] net: dsa: mv88e6xxx: various hwstamp fixes
From: Luke Howard @ 2026-07-19  5:30 UTC (permalink / raw)
  To: Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Vivien Didelot, Gregory CLEMENT, Andrew Lunn,
	Richard Cochran
  Cc: Cedric Jehasse, Kieran Tyrrell, Max Holtmann, Max Hunter,
	Christoph Mellauner, Simon Gapp, netdev, linux-kernel,
	Luke Howard

Three fixes for improving the reliably of hardware timestamp acquisition
on Marvell switches. In our tests this eliminated missed timestamps in
ptp4l, whilst also preserving PTP event and general message ordering on
switches that support embedded timestamps.

Note: squashing the two embedded timestamp patches into a single commit
may be desirable.

Signed-off-by: Luke Howard <lukeh@padl.com>
---
Changes in v3:
- Unset UDP checksum on all embedded timestamp cases
- Handle embedded PTP arrival times without worker dispatch
- Link to v2: https://patch.msgid.link/20260710-mv88e6xxx-ptp-fixes-v2-0-af97c38df247@padl.com

Changes in v2:
- Lineraize SKBs before retrieving embedded timestamp
- Hoist driver register lock out of per-frame loop
- Link to v1: https://patch.msgid.link/20260703-mv88e6xxx-ptp-fixes-v1-0-0138581889a9@padl.com

To: Andrew Lunn <andrew@lunn.ch>
To: Vladimir Oltean <olteanv@gmail.com>
To: "David S. Miller" <davem@davemloft.net>
To: Eric Dumazet <edumazet@google.com>
To: Jakub Kicinski <kuba@kernel.org>
To: Paolo Abeni <pabeni@redhat.com>
To: Richard Cochran <richardcochran@gmail.com>
Cc: netdev@vger.kernel.org
Cc: linux-kernel@vger.kernel.org

---
Luke Howard (3):
      net: dsa: mv88e6xxx: use ARRIVAL1 counter for all peer delay messages
      net: dsa: mv88e6xxx: embedded PTP timestamp support
      net: dsa: mv88e6xxx: apply embedded PTP arrival times inline

 drivers/net/dsa/mv88e6xxx/chip.c     |   4 ++
 drivers/net/dsa/mv88e6xxx/chip.h     |  15 +++-
 drivers/net/dsa/mv88e6xxx/hwtstamp.c | 134 +++++++++++++++++++++++++++++------
 drivers/net/dsa/mv88e6xxx/hwtstamp.h |  14 ++++
 drivers/net/dsa/mv88e6xxx/ptp.c      |  65 ++++++++++++++---
 drivers/net/dsa/mv88e6xxx/ptp.h      |   1 +
 6 files changed, 202 insertions(+), 31 deletions(-)
---
base-commit: f6f3b36c15ed44de1fbb44e645e4fae8c4a4453e
change-id: 20260630-mv88e6xxx-ptp-fixes-1732570b8829

Best regards,
--  
Luke Howard <lukeh@padl.com>


^ permalink raw reply

* Re: [PATCH net-next v2 2/2] net: stmmac: dwmac-rk: enable the reference clock for output mode
From: Jiaxing Hu @ 2026-07-19  3:46 UTC (permalink / raw)
  To: maxime.chevallier
  Cc: andrew, davem, edumazet, kuba, pabeni, heiko, netdev,
	linux-rockchip, linux-arm-kernel, linux-kernel
In-Reply-To: <da7d4ee0-8122-4839-b807-5d23ee6aeef7@bootlin.com>

> You've taken my review into account (thanks :) ), but you're failing to
> address Andrew's comment.
>
> The PHY should be the one requesting the MAC to output the refclk, in
> that case the MAC will act as a clock provider [...]

You are both right, and I am sorry I missed Andrew's point on v1. The MAC
should not own the PHY's clock.

I have dropped this series. Instead the motorcomm driver now enables the
reference clock itself, which is the consumer that needs it:

  https://lore.kernel.org/all/20260719034555.3623003-1-gahing@gahingwoo.com/

The device tree names the YT8531 explicitly so the PHY is instantiated
without an MDIO ID probe, which it cannot answer before that clock runs;
the DTS goes to the rockchip tree. Tested on an ArmSoM CM5-IO: the PHY
links at 1000 Mbit/s with no dwmac-rk change at all.

Thanks for the review,
Jiaxing

^ permalink raw reply

* [PATCH net-next] net: phy: motorcomm: enable the reference clock for YT8531
From: Jiaxing Hu @ 2026-07-19  3:45 UTC (permalink / raw)
  To: Frank.Sae, andrew, hkallweit1, linux, davem, edumazet, kuba,
	pabeni
  Cc: netdev, linux-kernel, maxime.chevallier, heiko, linux-rockchip

The YT8531 needs a 25 MHz reference. On boards without a local crystal
it is fed from the SoC, described as a clock on the PHY node. Get and
enable it in probe so the PHY is clocked before its registers are
accessed. The clock is optional, so crystal-clocked boards are
unaffected.

Signed-off-by: Jiaxing Hu <gahing@gahingwoo.com>
---
This supersedes "[PATCH net-next v2] net: stmmac: dwmac-rk: ..." which
enabled the clock on the MAC side; Andrew and Maxime rightly pointed out
that the PHY is the consumer and should enable it. That series is dropped.

The DTS that consumes this (rk3576-armsom-cm5) goes to the rockchip tree
separately; the clock is optional so this patch stands alone. Tested on
an ArmSoM CM5-IO: the YT8531 links at 1000 Mbit/s.
 drivers/net/phy/motorcomm.c | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/drivers/net/phy/motorcomm.c b/drivers/net/phy/motorcomm.c
index 708491bc1..2f85974a3 100644
--- a/drivers/net/phy/motorcomm.c
+++ b/drivers/net/phy/motorcomm.c
@@ -6,6 +6,7 @@
  * Author: Frank <Frank.Sae@motor-comm.com>
  */
 
+#include <linux/clk.h>
 #include <linux/etherdevice.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
@@ -1169,9 +1170,20 @@ static int yt8521_probe(struct phy_device *phydev)
 static int yt8531_probe(struct phy_device *phydev)
 {
 	struct device *dev = &phydev->mdio.dev;
+	struct clk *clk;
 	u16 mask, val;
 	u32 freq;
 
+	/*
+	 * A crystal-less YT8531 takes its 25 MHz reference from the SoC. When
+	 * that reference is described as a clock, enable it for the life of the
+	 * device. It is optional, so crystal-clocked boards are unaffected.
+	 */
+	clk = devm_clk_get_optional_enabled(dev, NULL);
+	if (IS_ERR(clk))
+		return dev_err_probe(dev, PTR_ERR(clk),
+				     "failed to get and enable the reference clock\n");
+
 	if (device_property_read_u32(dev, "motorcomm,clk-out-frequency-hz", &freq))
 		freq = YTPHY_DTS_OUTPUT_CLK_DIS;
 
-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH net-next] net: dsa: realtek: rtl8365mb: use devm_mutex_init
From: Qingfang Deng @ 2026-07-19  2:41 UTC (permalink / raw)
  To: Luiz Angelo Daros de Luca, Linus Walleij, Alvin Šipraga,
	Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni
  Cc: netdev, linux-kernel
In-Reply-To: <20260718-realtek_mutext-v1-1-1cd7beb20ae6@gmail.com>

Hi Luiz,

On 7/18/2026 1:59 PM, Luiz Angelo Daros de Luca wrote:
> mutex_destroy() is needed while debugging mutex. Instead of calling it
> directly on driver .remove(), just use devm.
> 
> Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
> ---
>   drivers/net/dsa/realtek/rtl8365mb_main.c | 15 ++++++++++++---
>   drivers/net/dsa/realtek/rtl83xx.c        | 14 +++++++++++---
>   2 files changed, 23 insertions(+), 6 deletions(-)

As you're fixing a missing teardown, and the two files were introduced 
at different times, you should split your patch into two (one for each 
file), re-target your series to the "net" tree, and add a Fixes tag 
respectively.

Best regards,
Qingfang

^ permalink raw reply

* Question: MII media mux for external PHY/SFP combo ports
From: git @ 2026-07-19  2:12 UTC (permalink / raw)
  To: maxime.chevallier; +Cc: andrew, netdev, olteanv

Hi Maxime,

Sorry for the duplicate email. The previous copy was rejected by the list because it contained HTML (default mode in Proton Mail).
This is a plain-text resend.

I am working on upstream support for the Banana Pi BPI-R4 Pro 8X and
would like guidance before writing any board-specific mux code.

The board [1] has two independent RJ45/SFP combo connectors. Each has
one SerDes lane, a GPIO-controlled passive external mux, one AS21010P
copper PHY endpoint, and one SFP cage endpoint. One mux is connected
to an MT7988 MAC. The other is connected to an MxL86252 DSA user port.

The downstream policy selects SFP when a module is present and copper
when the cage is empty. Each combo must keep one logical network port,
its MAC address, and its bridge/VLAN configuration across a media
transition. Switching either combo must not affect the other combo or
the remaining switch ports.

I have downstream reference code [3,4], but it polls MOD_DEF0 and
replaces driver-private MediaTek or DSA phylink state. I am treating it
only as bring-up evidence.

The phy_port documentation [2] added by commit 62518b5b3d8e describes
external MII mux topologies and identifies an MII mux framework as
future work.

Does runtime MII/media mux follow-up work already exist or is it
planned? If not, is phy_port the right starting point for a generic
design that covers both MAC and DSA consumers?

I can provide the exact DTS topology, downstream references, and a board
for copper/SFP hot-plug, traffic, and concurrent-combo testing.

[1] https://docs.banana-pi.org/en/BPI-R4_Pro/BananaPi_BPI-R4_Pro
[2] https://patch.msgid.link/20260108080041.553250-15-maxime.chevallier@bootlin.com
[3] https://github.com/BPI-SINOVOIP/BPI-R4PRO-8X-OPENWRT-V24.10.0-Master-Devel/blob/56e0e77adad258ba05782fee8f94f00d17b0b991/target/linux/mediatek/files-6.6/drivers/net/dsa/mxl862xx/mxl862xx.c#L3696-L3738
[4] https://github.com/frank-w/BPI-Router-Linux/blob/6aa3b285d8f88267e5f3f403000c88a08b61f851/drivers/net/ethernet/mediatek/mtk_eth_soc.c#L5430-L5508

Thanks,
Pavel Kirbai


^ permalink raw reply

* Re: [PATCH net-next v6 1/2] net: dsa: realtek: rtl8365mb: add SGMII support for RTL8367S
From: Luiz Angelo Daros de Luca @ 2026-07-19  1:09 UTC (permalink / raw)
  To: Stanislaw
  Cc: Johan Alvarado, Mieczyslaw Nalewaj, Linus Walleij, Alvin Sipraga,
	Andrew Lunn, Vladimir Oltean, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Russell King, Maxime Chevallier,
	open list:NETWORKING DRIVERS, open list
In-Reply-To: <20260718075549.91288-1-kuncy7@gmail.com>

> Hi Luiz, Mieczyslaw,
>
> > Stanislaw, do you have a reset pin or a reset controller configured?
>
> Yes - a reset GPIO, no reset controller:
>
>   reset-gpios = <&tlmm 33 GPIO_ACTIVE_LOW>;
>
> on the switch node. So on this board the probe path does run both
> resets: the GPIO hard reset from rtl83xx_probe() plus the soft reset
> during setup. That is worth stressing, because the re-probe that cures
> the cold-soak state performs *exactly the same sequence* - same GPIO
> reset, same soft reset, same full init - as the probe that lands in
> the bad state. The only difference is that it is the second run. So
> it does not look like a missing reset; more like something about the
> very first init pass on cold silicon.

That might eliminate the cause as the sw reset being less effective.

> On "loading too early": the driver is a module here and probes ~20 s
> after power-on (rootfs mount), so the rails have long been up by then.
> That said, 20 s of a cold board is a different thermal/electrical
> state than the same board a minute after a reboot, and short
> power-cycles (~1 min) never reproduce the issue - only a multi-hour
> soak does.

When I said too early, I meant other components that interact with the
switch. Maybe they are not ready. Try to skip the module loading
during boot and load it a minute (or more) later. If it works, we have
an indication that is not the switch.

> Next cold morning I'll do the reset-bit isolation on the degraded
> state, per Luiz's map, before falling back to the full re-probe.

Another test would be to dump all switch regs before the first reset
and compare what is different from the state after the reset. Maybe
depending on the previous switch state, it may require more time to
recover from reset. I suggest the regmap debugfs.

If the double probe works, you can also incrementally duplicate the
process until you reach the point where it works. My 2-cent guess is
the eee reg set by the jam table. And you could also add some sleeps
to check if it is time related or just state related. This is
fundamental to track the issue (state or time related).

It would also be great if you could isolate how long you have to keep
the device off to reproduce the issue. Try a simple binary search.
Reducing this test window will speed up the tests.

Regards,


> Best regards,
> Stanislaw

^ permalink raw reply

* Re: [PATCH 5/6] pds_core: add host backed memory support for firmware
From: Rao, Nikhil @ 2026-07-18 23:16 UTC (permalink / raw)
  To: Simon Horman
  Cc: netdev, kuba, brett.creeley, eric.joyner, andrew+netdev, davem,
	edumazet, pabeni, jacob.e.keller, nikhil.rao
In-Reply-To: <20260716072313.192902-1-horms@kernel.org>

On 7/16/2026 12:23 AM, Simon Horman wrote:
> This is an AI-generated review of your patch. The human sending this
> email has considered the AI review valid, or at least plausible.
> Full review at: https://sashiko.dev
> ---
> --- Patch [5]: [PATCH 5/6] pds_core: add host backed memory support for firmware ---
> commit 0970ce1c8fa02a37c722e99472916638286b6ad9
> Author: Nikhil P. Rao <nikhil.rao@amd.com>
> 
> pds_core: add host backed memory support for firmware
> 
> Some newer AMD/Pensando cards have minimal memory and there are cases
> where components, specifically in the control plane, need more memory.
> This series adds support for host backed DMA memory that can be used
> by the firmware for the previously mentioned cases.
> 
>> diff --git a/drivers/net/ethernet/amd/pds_core/core.c b/drivers/net/ethernet/amd/pds_core/core.c
>> index a7a0bcf98ed3f..356a69c895b63 100644
>> --- a/drivers/net/ethernet/amd/pds_core/core.c
>> +++ b/drivers/net/ethernet/amd/pds_core/core.c
> 
> [ ... ]
> 
>> @@ -673,3 +675,161 @@ void pdsc_health_thread(struct work_struct *work)
> 
> [ ... ]
> 
>> +static int pdsc_host_mem_add_one(struct pdsc *pdsc, int index)
>> +{
>> +     struct pdsc_host_mem *hm = &pdsc->host_mem_reqs[index];
>> +     union pds_core_dev_comp comp = {};
>> +     union pds_core_dev_cmd cmd = {};
>> +     int err;
> 
> [ ... ]
> 
>> +     /* Track this allocation so pdsc_host_mem_free() can clean it up */
>> +     pdsc->num_host_mem_reqs++;
>> +
>> +     memset(&cmd, 0, sizeof(cmd));
>> +     memset(&comp, 0, sizeof(comp));
>> +     cmd.host_mem.opcode = PDS_CORE_CMD_HOST_MEM;
>> +     cmd.host_mem.oper = PDS_CORE_HOST_MEM_ADD;
>> +     cmd.host_mem.tag = cpu_to_le16(hm->tag);
>> +     cmd.host_mem.size = cpu_to_le32(hm->size);
>> +     cmd.host_mem.buf_pa = cpu_to_le64(hm->pa);
>> +
>> +     dev_dbg(pdsc->dev, "Sending devcmd for mem add tag %d size %d pa %pad\n",
>> +             hm->tag, hm->size, &hm->pa);
>> +     err = pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout);
>> +     if (err || comp.status != PDS_RC_SUCCESS) {
>> +             dev_err(pdsc->dev, "mem add failed err %d status %d for tag %d\n",
>> +                     err, comp.status, hm->tag);
>> +             err = err ? err : -EIO;
>> +             goto err_del;
>> +     }
>> +     dev_dbg(pdsc->dev, "mem add completed for tag %d\n", hm->tag);
>> +
>> +     return 0;
>> +
>> +err_del:
>> +     /* After MEM_QUERY succeeds, firmware expects MEM_ADD or MEM_DEL */
>> +     pdsc_host_mem_del_one(pdsc, hm->tag, PDS_RC_ENOMEM);
>> +     return err;
>> +}
> 
> [Severity: Medium]
> When the MEM_ADD device command fails, does this error path leave the memory
> allocated and DMA mapped?
> 
> Since pdsc->num_host_mem_reqs was incremented before the command, the memory
> will eventually be freed when pdsc_host_mem_free() runs at driver teardown.
> However, it appears up to 4MB of memory per failed request could be held
> uselessly for the entire operational runtime of the driver.
> 
> Should the driver explicitly unmap and free the memory in this error path?
> 
> [ ... ]
Same response as in [1]

This is intentional. In the timeout case, firmware could still be
accessing the memory (slow response, not actual failure). Freeing
immediately could cause firmware to DMA into freed memory. The memory
is freed at teardown regardless of MEM_ADD result.

> 
>> +void pdsc_host_mem_free(struct pdsc *pdsc)
>> +{
>> +     int i;
>> +
>> +     if (!pdsc->host_mem_reqs)
>> +             return;
>> +
>> +     for (i = 0; i < pdsc->num_host_mem_reqs; i++) {
>> +             dma_unmap_page(pdsc->dev, pdsc->host_mem_reqs[i].pa,
>> +                            pdsc->host_mem_reqs[i].size,
>> +                            DMA_BIDIRECTIONAL);
>> +             __free_pages(pdsc->host_mem_reqs[i].pg,
>> +                          pdsc->host_mem_reqs[i].order);
>> +     }
>> +
>> +     kfree(pdsc->host_mem_reqs);
>> +     pdsc->host_mem_reqs = NULL;
>> +     pdsc->num_host_mem_reqs = 0;
>> +}
> 
> [Severity: High]
> Does this code risk leaving the firmware with dangling DMA addresses?
> 
> The loop unmaps and frees the provisioned host memory pages, but it doesn't
> appear to send a corresponding PDS_CORE_HOST_MEM_DEL device command to notify
> the firmware that the memory has been revoked.
> 
> While pdsc_teardown() sends an explicit reset command for Physical Functions,
> it skips this step for Virtual Functions. If a VF frees memory without sending
> a MEM_DEL command, could the firmware still attempt to access the freed memory,
> resulting in IOMMU faults or use-after-free corruption?
Same response as in [1].

For PFs: pdsc_devcmd_reset() runs before pdsc_host_mem_free() in
teardown. The reset clears all firmware state including host memory
state, so individual MEM_DEL commands are not needed.

For VFs: They never allocate host memory. VF init path (pdsc_init_vf())
doesn't call pdsc_setup() or pdsc_start(). pdsc_host_mem_add() is only
called from pdsc_start() in pdsc_init_pf().

Thanks,
Nikhil

[1] 
https://lore.kernel.org/netdev/e5c3a2f0-4104-4e3b-a85e-a6899f662169@amd.com/

^ permalink raw reply

* Re: [PATCH 4/6] pds_core: add PLDM component info display
From: Rao, Nikhil @ 2026-07-18 23:06 UTC (permalink / raw)
  To: Simon Horman
  Cc: netdev, kuba, brett.creeley, eric.joyner, andrew+netdev, davem,
	edumazet, pabeni, jacob.e.keller, nikhil.rao
In-Reply-To: <20260716072248.190353-2-horms@kernel.org>

On 7/16/2026 12:22 AM, Simon Horman wrote:
> This is an AI-generated review of your patch. The human sending this
> email has considered the AI review valid, or at least plausible.
> Full review at: https://sashiko.dev
> ---
> --- Patch [4]: [PATCH 4/6] pds_core: add PLDM component info display ---
> commit 4d0cc8aef76c7e2e30f98535495312200941b646
> Author: Brett Creeley <brett.creeley@amd.com>
> 
> pds_core: add PLDM component info display
> 
> Add detailed component information display via devlink info. This
> allows users to see individual firmware components and their versions.
> Components are reported as fixed, running, or stored based on their
> firmware-provided flags.
> 
>> diff --git a/drivers/net/ethernet/amd/pds_core/devlink.c b/drivers/net/ethernet/amd/pds_core/devlink.c
>> index 3b763ee1715ee..63fe45e91f710 100644
>> --- a/drivers/net/ethernet/amd/pds_core/devlink.c
>> +++ b/drivers/net/ethernet/amd/pds_core/devlink.c
> 
> [ ... ]
> 
>> +static int pdsc_dl_component_info_get(struct devlink *dl,
>> +                                   struct devlink_info_req *req,
>> +                                   struct netlink_ext_ack *extack)
>> +{
>> +     struct pdsc *pdsc = devlink_priv(dl);
>> +     u8 num_components;
>> +     int err;
>> +     int i;
>> +
>> +     /* Pairs with WRITE_ONCE in pdsc_fw_components_invalidate().
>> +      * Use READ_ONCE to get a consistent snapshot of num_components.
>> +      * pdsc_fw_components_invalidate() can zero it concurrently during
>> +      * firmware recovery; using the local copy avoids iterating zero
>> +      * times when we already decided the cache was valid.
>> +      */
>> +     num_components = READ_ONCE(pdsc->fw_components.num_components);
>> +     if (!num_components) {
>> +             err = pdsc_get_component_info(pdsc);
> 
> [Severity: High]
> Does this code expose uninitialized memory to concurrent readers?
> 
> If two threads execute this concurrently after the firmware component list
> has been invalidated, one thread could enter pdsc_get_component_info(),
> which updates the count before the array is populated:
> 
> drivers/net/ethernet/amd/pds_core/fw.c:pdsc_get_component_info() {
>      ...
>          pdsc->fw_components.num_components = num_components;
>          for (i = 0; i < num_components; i++) {
>                  struct pds_core_fw_component_info *info =
>                          &pdsc->fw_components.info[i];
> 
>                  memcpy(info, &list_info->info[i], sizeof(*info));
>      ...
> }
> 
> A second thread could concurrently execute pdsc_dl_component_info_get(), see
> num_components > 0 via READ_ONCE(), and proceed to iterate over the array
> while the first thread is still executing memcpy() into the array.
> 
> Could this be addressed by moving the update of num_components to after the
> array population is complete, perhaps using smp_store_release() and
> smp_load_acquire() to ensure proper ordering?
> 
>> +             if (err)
>> +                     return pdsc_dl_report_fw_ver(req,
>> +                                                 pdsc->dev_info.fw_version);
>> +             num_components = READ_ONCE(pdsc->fw_components.num_components);
>> +             if (!num_components)
>> +                     return pdsc_dl_report_fw_ver(req,
>> +                                                 pdsc->dev_info.fw_version);
>> +     }
>> +
>> +     num_components = min_t(u16, num_components,
>> +                            le16_to_cpu(pdsc->dev_ident.max_fw_slots));
>> +     for (i = 0; i < num_components; i++) {
>> +             err = pdsc_dl_report_component(req,
>> +                                            &pdsc->fw_components.info[i]);
>> +             if (err)
>> +                     return err;
>> +     }
>> +
>> +     return 0;
>> +}
Not a bug. devl_lock serializes the .info_get
ops on the instance, so the two concurrent executions of 
pdsc_dl_component_info_get described above cannot occur

Thanks,
Nikhil

^ permalink raw reply

* Re: [PATCH 3/6] pds_core: add PLDM firmware update support via devlink flash
From: Rao, Nikhil @ 2026-07-18 22:34 UTC (permalink / raw)
  To: Simon Horman
  Cc: netdev, kuba, brett.creeley, eric.joyner, andrew+netdev, davem,
	edumazet, pabeni, jacob.e.keller, nikhil.rao
In-Reply-To: <20260716072042.186444-2-horms@kernel.org>

On 7/16/2026 12:20 AM, Simon Horman wrote:
> This is an AI-generated review of your patch. The human sending this
> email has considered the AI review valid, or at least plausible.
> Full review at: https://sashiko.dev
> ---
> --- Patch [3]: [PATCH 3/6] pds_core: add PLDM firmware update support via devlink flash ---
> commit 865af5a5208d8bbf12e2bc16faaacfc3c3239978
> Author: Brett Creeley <brett.creeley@amd.com>
> 
> pds_core: add PLDM firmware update support via devlink flash
> 
> This commit implements PLDM firmware update support in the pds_core driver
> using the upstream pldmfw API. It allows updating an entire PLDM FW package
> at once or updating specific firmware components by name.
> 
>> diff --git a/drivers/net/ethernet/amd/pds_core/dev.c b/drivers/net/ethernet/amd/pds_core/dev.c
>> index 84ea502ecb128..b149d29bd2560 100644
>> --- a/drivers/net/ethernet/amd/pds_core/dev.c
>> +++ b/drivers/net/ethernet/amd/pds_core/dev.c
>> @@ -206,15 +206,56 @@ static int __pdsc_devcmd_locked(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,
>>        else
>>                memcpy_fromio(comp, &pdsc->cmd_regs->comp, sizeof(*comp));
>>
>> +     if (err != -ETIMEDOUT && err != -EAGAIN)
>> +             pdsc_deferred_dma_free(pdsc);
>> +
> 
> [Severity: High]
> Can pdsc_deferred_dma_free() prematurely free DMA memory that the device
> firmware is still actively accessing?
> 
> If a DMA-based devcmd like pdsc_devcmd_send_component() times out after
> polling and returns -EAGAIN, the DMA mapping is added to
> pdsc->deferred_dma_list in pdsc_flash_component_chunk(). The firmware update
> aborts on the host, but the device might still be processing the command in
> the background.
> 
> If an unrelated devcmd (e.g., from a periodic health check) executes and
> succeeds shortly after, this logic will unconditionally unmap and free all
> deferred DMA memory. Does this lead to memory corruption or IOMMU faults if
> the firmware continues to access the freed memory?
> 
>>        return err;
>>   }
>>
>> +void pdsc_deferred_dma_add(struct pdsc *pdsc, struct pdsc_deferred_dma *entry,
>> +                        dma_addr_t dma_addr, void *va, size_t size,
>> +                        enum dma_data_direction dir)
>> +{
> 
The free is not premature. The devcmd interface processes one command at 
a time in firmware. A (non-EAGAIN/non-ETIMEDOUT) completion of any 
devcmd proves the prior DMA is quiesced.

> 
>> @@ -227,6 +268,47 @@ int pdsc_devcmd(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,
>>        return err;
>>   }
>>
>> +static int __pdsc_devcmd_with_data(struct pdsc *pdsc,
>> +                                union pds_core_dev_cmd *cmd,
>> +                                const void *data, size_t data_len,
>> +                                union pds_core_dev_comp *comp,
>> +                                int max_seconds, bool do_msg)
>> +{
>> +     int err;
>> +
>> +     mutex_lock(&pdsc->devcmd_lock);
>> +     if (!pdsc->cmd_regs) {
>> +             err = -ENXIO;
>> +             goto unlock;
>> +     }
>> +     if (data_len > sizeof(pdsc->cmd_regs->data)) {
>> +             err = -ENOSPC;
>> +             goto unlock;
>> +     }
>> +     memcpy_toio(&pdsc->cmd_regs->data, data, data_len);
> 
> [Severity: High]
> This is a pre-existing issue, but can a concurrent PCI reset unmap the device
> BARs while the firmware update is actively copying data to MMIO here?
> 
> When devlink flash initiates a firmware update, __pdsc_devcmd_with_data()
> acquires devcmd_lock and begins copying up to 2KB of data via memcpy_toio().
> 
> Concurrently, a PCI reset (e.g., via sysfs or AER) triggers
> pdsc_reset_prepare(), which calls pdsc_unmap_bars() without acquiring
> devcmd_lock. pdsc_unmap_bars() sets pdsc->cmd_regs = NULL and calls
> pci_iounmap().
> 
> Could this race cause a kernel panic if the firmware update thread dereferences
> the now-NULL pdsc->cmd_regs pointer or accesses unmapped MMIO memory? The long
> polling firmware update that repeatedly copies large data blocks might
> exacerbate this race window.
This is a valid race. Since this is a pre-existing issue, I will submit 
a fix to -net.

Thanks,
Nikhil

^ permalink raw reply

* Re: [PATCH net-next v2 0/4] genetlink: apply reject policy for split ops on the dispatch path
From: Vladimir Oltean @ 2026-07-18 22:25 UTC (permalink / raw)
  To: Jakub Kicinski, andrew+netdev, Jiri Pirko, Donald Hunter
  Cc: davem, netdev, edumazet, pabeni, horms, shuah, linux-kselftest
In-Reply-To: <20260311032839.417748-1-kuba@kernel.org>

Hi Jakub,

On Tue, Mar 10, 2026 at 08:28:35PM -0700, Jakub Kicinski wrote:
> Looks like I somehow missed adding default reject policies to commands
> in families using split Netlink ops. I realized this randomly trying
> to dump page pools for a specific device and always getting all of them
> back. The per-device dump is simply not implemented so the request
> should have been rejected. Patch 2 is the real change, the rest is
> just accompaniment.
> 
> v2:
>  - add patch 1 to avoid breaking devlink
>  - add a lot more tests
> v1: https://lore.kernel.org/20260307204425.1900467-1-kuba@kernel.org
> 
> Jakub Kicinski (4):
>   genetlink: use maxattr of 0 for the reject policy
>   genetlink: apply reject policy for split ops on the dispatch path
>   selftests: net: make sure that Netlink rejects unknown attrs in dump
>   selftests: net: add test for Netlink policy dumps
> 
>  tools/testing/selftests/net/Makefile          |   1 +
>  net/netlink/genetlink.c                       |  20 +--
>  net/netlink/policy.c                          |   4 +-
>  .../testing/selftests/net/lib/py/__init__.py  |   5 +-
>  tools/testing/selftests/net/lib/py/ynl.py     |  10 +-
>  tools/testing/selftests/net/nl_netdev.py      |  32 ++++-
>  tools/testing/selftests/net/nl_nlctrl.py      | 135 ++++++++++++++++++
>  7 files changed, 186 insertions(+), 21 deletions(-)
>  create mode 100755 tools/testing/selftests/net/nl_nlctrl.py
> 
> -- 
> 2.53.0
> 
> 

It looks like this patch set broke at least filtered DEVLINK_CMD_GET/
DEVLINK_CMD_INFO_GET dumps in general and mv88e6xxx_dump in particular
(https://github.com/lunn/mv88e6xxx_dump).

I didn't follow the split ops development, but here it seems that the
idea to reject unknown attributes in dumps at the genetlink core layer
interacts negatively with devlink's use of split ops. See

int devlink_nl_dumpit(struct sk_buff *msg, struct netlink_callback *cb,
		      devlink_nl_dump_one_func_t *dump_one)
{
	const struct genl_info *info = genl_info_dump(cb);
	struct nlattr **attrs = info->attrs;
	int flags = NLM_F_MULTI;

	if (attrs &&								\
	    (attrs[DEVLINK_ATTR_BUS_NAME] || attrs[DEVLINK_ATTR_DEV_NAME] ||	|
	     attrs[DEVLINK_ATTR_INDEX]))					| Your patch is shunting
		return devlink_nl_inst_single_dumpit(msg, cb, flags, dump_one,	| this entire branch
						     attrs);			|
	else									/
		return devlink_nl_inst_iter_dumpit(msg, cb, flags, dump_one);   // only this will work
}

In mv88e6xxx_dump we have the following affected pattern
(DEVLINK_CMD_INFO_GET netlink command with the NLM_F_DUMP flag which
also attaches attributes):

static void get_info(struct mv88e6xxx_ctx *ctx)
{
	struct nlmsghdr *nlh;
	uint16_t flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP;
	int err;

	nlh = mnlg_msg_prepare(ctx->nlg, DEVLINK_CMD_INFO_GET, flags);
	mnl_attr_put_strz(nlh, DEVLINK_ATTR_BUS_NAME, ctx->bus_name);
	mnl_attr_put_strz(nlh, DEVLINK_ATTR_DEV_NAME, ctx->dev_name);

	err = _mnlg_socket_sndrcv(ctx->nlg, nlh, get_info_cb, ctx);
	if (err) {
		printf("Unable to get devices info\n");
		exit(EXIT_FAILURE);
	}
}

In the kernel, the devlink_nl_ops[] split_ops item for DEVLINK_CMD_INFO_GET
is _not_ defined with a policy for attributes. So your patch rejects it,
and that causes the regression.

I didn't review the entire devlink code, but I did review the entire
mv88e6xxx_dump code, and that is the only breakage.

We also have the following unaffected user space calls:

1.
static void list(struct mv88e6xxx_ctx *ctx)
{
	struct nlmsghdr *nlh;
	uint16_t flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP;

	nlh = mnlg_msg_prepare(ctx->nlg, DEVLINK_CMD_GET, flags);
	_mnlg_socket_sndrcv(ctx->nlg, nlh, list_cb, ctx);
}

The split_op for DEVLINK_CMD_GET also does not have a policy for
attributes, but we do not populate attributes (OK). The code does go
through devlink_nl_dumpit(), so the regression potential is there, it is
just not triggered with mv88e6xxx_dump.

2.
static void delete_snapshots(struct mv88e6xxx_ctx *ctx)
{
	struct nlmsghdr *nlh;
	uint16_t flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP;

	/* Sending a new message while decoding an older message
	 * results in problems. So keep repeating until all regions
	 * snapshots are gone. */
	do {
		ctx->repeat = false;
		nlh = mnlg_msg_prepare(ctx->nlg, DEVLINK_CMD_REGION_GET, flags);
		mnl_attr_put_strz(nlh, DEVLINK_ATTR_BUS_NAME, ctx->bus_name);
		mnl_attr_put_strz(nlh, DEVLINK_ATTR_DEV_NAME, ctx->dev_name);

		_mnlg_socket_sndrcv(ctx->nlg, nlh, delete_snapshots_cb, ctx);
	} while (ctx->repeat);
}

The split_op for DEVLINK_CMD_REGION_GET *does* have a policy for the
attributes that we add to this command when emitting it with NLM_F_DUMP.

3.
static int dump_snapshot_port_id(struct mv88e6xxx_ctx *ctx,
				 uint32_t port, const char *region_name,
				 uint32_t id)
{
	struct nlmsghdr *nlh;
	int err;

	nlh = mnlg_msg_prepare(ctx->nlg, DEVLINK_CMD_REGION_READ,
			       NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP);

	mnl_attr_put_strz(nlh, DEVLINK_ATTR_BUS_NAME, ctx->bus_name);
	mnl_attr_put_strz(nlh, DEVLINK_ATTR_DEV_NAME, ctx->dev_name);
	if (port != ~0)
		mnl_attr_put_u32(nlh, DEVLINK_ATTR_PORT_INDEX, port);
	mnl_attr_put_strz(nlh, DEVLINK_ATTR_REGION_NAME, region_name);
	mnl_attr_put_u32(nlh, DEVLINK_ATTR_REGION_SNAPSHOT_ID, id);

	err = _mnlg_socket_sndrcv(ctx->nlg, nlh, dump_snapshot_cb, ctx);
	if (err)
		printf("Unable to dump snapshot %s\n", region_name);

	return err;
}

Same discussion for DEVLINK_CMD_REGION_READ. It does have a policy for
attributes.


I'm not sending a patch because I don't know what patch to send.

There are multiple directions, really.

(a) semantically, I don't know whether what get_info() does makes a lot
of sense. It requests NLM_F_DUMP, requesting a listing of all attributes,
and it then also passes DEVLINK_ATTR_BUS_NAME and DEVLINK_ATTR_DEV_NAME,
to filter for a single device out of that dump. That should be semantically
equivalent to not specifying NLM_F_DUMP, because in the kernel that goes
through the doit split op for DEVLINK_CMD_INFO_GET (which does have a policy),
not through the dumpit one. And it even makes more sense - instead of
dumping and telling the kernel to filter, just get the info for the
device to which you already have a handle.

I'm personally OK with saying user space is odd in this case, and fixing it.
If Andrew is OK too and will accept a patch removing NLM_F_DUMP from
get_info(), I can send this to mv88e6xxx_dump.

But technically it is an UAPI regression, so we should take that at face
value. Here is where the problems begin - I haven't reviewed all
possible dumpit commands, and I don't have time to do so.

(b) we can exempt all legacy split ops from your blanket policy rejection.
Essentially make genl_op_fill_in_reject_policy_split() do the same thing
as genl_op_fill_in_reject_policy():

diff --git a/net/netlink/genetlink.c b/net/netlink/genetlink.c
index 0da39eaed255..61d249b9b089 100644
--- a/net/netlink/genetlink.c
+++ b/net/netlink/genetlink.c
@@ -114,7 +114,7 @@ static void
 genl_op_fill_in_reject_policy_split(const struct genl_family *family,
 				    struct genl_split_ops *op)
 {
-	if (op->policy)
+	if (op->policy || op->cmd < family->resv_start_op)
 		return;
 
 	op->policy = genl_policy_reject_all;

I think you are not in favour of this approach, because in commit
e3a5b7f8ef2a ("genetlink: apply reject policy for split ops on the
dispatch path") you said:

    If anyone reports issues we should probably fill in fake policies
    for specific ops rather than reverting this.

so in case that remains your desire, we have the following direction:

(c) add attributes to the netlink schema of all devlink commands that
support dumps (they all go through devlink_nl_dumpit() which parses them).

Essentially the patch below, generated with larger -U25 context for
clarity, and the tools/net/ynl/ynl-regen.sh output omitted for brevity.

--- a/Documentation/netlink/specs/devlink.yaml
+++ b/Documentation/netlink/specs/devlink.yaml
@@ -1318,50 +1318,52 @@ operations:
   enum-model: directional
   list:
     -
       name: get
       doc: Get devlink instances.
       attribute-set: devlink
       dont-validate: [strict, dump]
       do:
         pre: devlink-nl-pre-doit
         post: devlink-nl-post-doit
         request:
           value: 1
           attributes: &dev-id-attrs
             - bus-name
             - dev-name
             - index
         reply: &get-reply
           value: 3
           attributes:
             - bus-name
             - dev-name
             - index
             - reload-failed
             - dev-stats
       dump:
+        request:
+          attributes: *dev-id-attrs
         reply: *get-reply

     -
       name: port-get
       doc: Get devlink port instances.
       attribute-set: devlink
       dont-validate: [strict]
       do:
         pre: devlink-nl-pre-doit-port
         post: devlink-nl-post-doit
         request:
           value: 5
           attributes: &port-id-attrs
             - bus-name
             - dev-name
             - index
             - port-index
         reply:
           value: 7
           attributes: *port-id-attrs
       dump:
         request:
           attributes: *dev-id-attrs
         reply:
           value: 3  # due to a bug, port dump returns DEVLINK_CMD_NEW
@@ -1984,50 +1986,52 @@ operations:
       name: info-get
       doc: |
         Get device information, like driver name, hardware and firmware versions
         etc.
       attribute-set: devlink
       dont-validate: [strict, dump]
       do:
         pre: devlink-nl-pre-doit
         post: devlink-nl-post-doit
         request:
           value: 51
           attributes: *dev-id-attrs
         reply: &info-get-reply
           value: 51
           attributes:
             - bus-name
             - dev-name
             - index
             - info-driver-name
             - info-serial-number
             - info-version-fixed
             - info-version-running
             - info-version-stored
             - info-board-serial-number
       dump:
+        request:
+          attributes: *dev-id-attrs
         reply: *info-get-reply

     -
       name: health-reporter-get
       doc: Get health reporter instances.
       attribute-set: devlink
       dont-validate: [strict]
       do:
         pre: devlink-nl-pre-doit-port-optional
         post: devlink-nl-post-doit
         request:
           attributes: &health-reporter-id-attrs
             - bus-name
             - dev-name
             - index
             - port-index
             - health-reporter-name
         reply: &health-reporter-get-reply
           attributes: *health-reporter-id-attrs
       dump:
         request:
           attributes: *port-id-attrs
         reply: *health-reporter-get-reply

     -

But this is where things get a bit more interesting. Technically only
devlink info-get regressed in mv88e6xxx_dump. Do we fix devlink get too?
How far should this investigation go? I don't really want to go into a
very deep rabbit hole. In this sense option (b) may be preferable if
avoiding UAPI regressions is our main objective.

Thanks for reading what turned out a rather long email, and sorry if I
got anything wrong. I'm not a netlink expert by any stretch of imagination.

^ permalink raw reply related

* Re: [PATCH 1/6] pds_core: add support for quiet devcmd failures
From: Rao, Nikhil @ 2026-07-18 22:21 UTC (permalink / raw)
  To: Simon Horman
  Cc: netdev, kuba, brett.creeley, eric.joyner, andrew+netdev, davem,
	edumazet, pabeni, jacob.e.keller, nikhil.rao
In-Reply-To: <20260716071736.179693-3-horms@kernel.org>


On 7/16/2026 12:17 AM, Simon Horman wrote:
> This is an AI-generated review of your patch. The human sending this
> email has considered the AI review valid, or at least plausible.
> Full review at: https://sashiko.dev
> ---
> --- Patch [1]: [PATCH 1/6] pds_core: add support for quiet devcmd failures ---
> commit 2618e77f5d1aae31c9817f09b5056a69e237162a
> Author: Brett Creeley <brett.creeley@amd.com>
> 
> pds_core: add support for quiet devcmd failures
> 
> This commit adds support to allow devcmd failure messages to be suppressed.
> This will be used when adding support to negotiate
> PDS_CORE_IDENTITY_VERSION_2.
> 
>> diff --git a/drivers/net/ethernet/amd/pds_core/dev.c b/drivers/net/ethernet/amd/pds_core/dev.c
>> index bded6b33289ce..dd9989cfe6b3f 100644
>> --- a/drivers/net/ethernet/amd/pds_core/dev.c
>> +++ b/drivers/net/ethernet/amd/pds_core/dev.c
> 
> [ ... ]
> 
>> @@ -197,7 +199,7 @@ int pdsc_devcmd_locked(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,
>>
>>        memcpy_toio(&pdsc->cmd_regs->cmd, cmd, sizeof(*cmd));
>>        pdsc_devcmd_dbell(pdsc);
>> -     err = pdsc_devcmd_wait(pdsc, cmd->opcode, max_seconds);
>> +     err = __pdsc_devcmd_wait(pdsc, cmd->opcode, max_seconds, do_msg);
>>
>>        if ((err == -ENXIO || err == -ETIMEDOUT) && pdsc->wq)
>>                queue_work(pdsc->wq, &pdsc->health_work);
> 
> [Severity: High]
> This is a pre-existing issue, but does this error path trigger a
> use-after-free during device removal?
> 
> During device removal, pdsc_remove() destroys the workqueue without clearing
> the pointer before invoking pdsc_teardown():
> 
> pdsc_remove() {
>          ...
>          if (pdsc->wq)
>                  destroy_workqueue(pdsc->wq);
> 
>          mutex_lock(&pdsc->config_lock);
>          set_bit(PDSC_S_STOPPING_DRIVER, &pdsc->state);
> 
>          pdsc_stop(pdsc);
>          pdsc_teardown(pdsc, PDSC_TEARDOWN_REMOVING);
>          ...
> }
> 
> The teardown function issues a hardware reset command via pdsc_devcmd_reset().
> If the device is unresponsive and the command times out, the error path here in
> __pdsc_devcmd_locked() catches the -ETIMEDOUT error.
> 
> Because pdsc->wq was left as a dangling pointer, the condition evaluates to true
> and execution falls through to queue_work(), which will dereference the already
> destroyed workqueue pointer.
> 
> Could the pdsc->wq pointer be set to NULL immediately after destruction to
> prevent this, or should the teardown sequence be reordered?
> 
>>        else
>>                memcpy_fromio(comp, &pdsc->cmd_regs->comp, sizeof(*comp));
>>
>>        if (err != -ETIMEDOUT && err != -EAGAIN)
>>                pdsc_deferred_dma_free(pdsc);
>>
>>        return err;
>>   }

Fixed in [1] by reordering destroy_workqueue() after pdsc_teardown().

Thanks,
Nikhil

[1] 
https://lore.kernel.org/netdev/20260714180223.1642792-3-nikhil.rao@amd.com/

^ permalink raw reply

* Re: [PATCH net-next] net: stmmac: Simplify ioctl handling
From: Vadim Fedorenko @ 2026-07-18 21:19 UTC (permalink / raw)
  To: Maxime Chevallier, Andrew Lunn, Jakub Kicinski, davem,
	Eric Dumazet, Paolo Abeni, Simon Horman, Maxime Coquelin,
	Alexandre Torgue, Russell King
  Cc: thomas.petazzoni, Alexis Lothoré, netdev, linux-kernel,
	linux-arm-kernel, linux-stm32
In-Reply-To: <20260718143848.677531-1-maxime.chevallier@bootlin.com>

On 18.07.2026 15:38, Maxime Chevallier wrote:
> Now that timestamping is controlled through an NDO, we can simply
> call phylink_mii_ioctl() to handle ioctls.
> 
> The only functional difference is that phylink_mii_ioctl() ->
> phy_mii_ioctl() can handle SIOCSHWTSTAMP, but this no longer happens
> as this ioctl is not longer dispatched to the ndo_eth_ioctl().
> 
> Signed-off-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
> ---
> 
> Looking at this, I'm wondering if we can't just get rid of SIOCSHWTSTAMP
> handling in phy_mii_ioctl(). Looks like we can ?
> 
>   .../net/ethernet/stmicro/stmmac/stmmac_main.c   | 17 +++--------------
>   1 file changed, 3 insertions(+), 14 deletions(-)
> 
> diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> index 2a0d7eff88d3..562d20830b94 100644
> --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> @@ -6371,28 +6371,17 @@ static irqreturn_t stmmac_msi_intr_rx(int irq, void *data)
>    *  @rq: An IOCTL specific structure, that can contain a pointer to
>    *  a proprietary structure used to pass information to the driver.
>    *  @cmd: IOCTL command
> - *  Description:
> - *  Currently it supports the phy_mii_ioctl(...) and HW time stamping.
> + *  Description: Forward the PHY ioctls to phylink
> + *  Return: Zero on success or negative error code.
>    */
>   static int stmmac_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
>   {
>   	struct stmmac_priv *priv = netdev_priv (dev);
> -	int ret = -EOPNOTSUPP;
>   
>   	if (!netif_running(dev))
>   		return -EINVAL;
>   
> -	switch (cmd) {
> -	case SIOCGMIIPHY:
> -	case SIOCGMIIREG:
> -	case SIOCSMIIREG:
> -		ret = phylink_mii_ioctl(priv->phylink, rq, cmd);
> -		break;
> -	default:
> -		break;
> -	}
> -
> -	return ret;
> +	return phylink_mii_ioctl(priv->phylink, rq, cmd);
>   }
>   
>   static int stmmac_setup_tc_block_cb(enum tc_setup_type type, void *type_data,

Reviewed-by: Vadim Fedorenko <vadim.fedorenko@linux.dev>

^ permalink raw reply

* [RFC PATCH net-next v0.1 1/1] net: add GeoNetworking protocol
From: Simon Dietz @ 2026-07-18 21:00 UTC (permalink / raw)
  To: netdev
  Cc: andrew+netdev, davem, edumazet, johannes, kuniyu, linux-wireless,
	simon.dietz, dietz23838
In-Reply-To: <20260718210046.2357882-1-simon.dietz@plantwatch.de>

Implement the GeoNetworking / ETSI ITS-G5 ('net/gn') protocol which
is based on 802.11p wifi and used for vehicle2x applications. It is
standardized by the ETSI and used by some car manufacturers
(especially in europe). It enables ad-hoc, multi-hop geographical
communication and routing among vehicles (and road- or railside
infrastructure).

Most work of this implementation has been done by the bachelor
project 2018/2019 of the operating systems and middleware group of
the Hasso Plattner Institute, University of Potsdam, which the author
was part of.

The code is published under the GPL v2 at this location:
https://gitlab.com/hpi-potsdam/osm/g5-on-linux/linux-geonetworking/

This patch set is not merge-ready, yet. E.g. network namespaces are
not supported at all, documentation is missing, ...

Purpose of this RFC is to determine if GeoNetworking support in the
linux kernel is desirable and if so what steps would have to be done
next.

In the original implementation new ioctl calls have been used. For the
removal of them and changing their functionality to use netlink (and
only this change) help of AI has been used as stated below.

Signed-off-by: Simon Dietz <simon.dietz@plantwatch.de>
Assisted-by: Gemini:gemini-3.1-pro antigravity-ide
---
 include/linux/gn.h                  |  363 +++++
 include/linux/gn_routing.h          |   68 +
 include/linux/socket.h              |    6 +-
 include/uapi/linux/gn.h             |   84 ++
 include/uapi/linux/if_ether.h       |    1 +
 include/uapi/linux/if_link.h        |   10 +
 net/Kconfig                         |    2 +
 net/Makefile                        |    1 +
 net/gn/Kconfig                      |    4 +
 net/gn/Makefile                     |    7 +
 net/gn/gn_netlink.c                 |  266 ++++
 net/gn/gn_prot.c                    | 1908 +++++++++++++++++++++++++++
 net/gn/gn_routing.c                 |  646 +++++++++
 net/gn/sysctl_net_gn.c              |   33 +
 security/selinux/hooks.c            |    5 +-
 security/selinux/include/classmap.h |    3 +-
 16 files changed, 3403 insertions(+), 4 deletions(-)
 create mode 100644 include/linux/gn.h
 create mode 100644 include/linux/gn_routing.h
 create mode 100644 include/uapi/linux/gn.h
 create mode 100644 net/gn/Kconfig
 create mode 100644 net/gn/Makefile
 create mode 100644 net/gn/gn_netlink.c
 create mode 100644 net/gn/gn_prot.c
 create mode 100644 net/gn/gn_routing.c
 create mode 100644 net/gn/sysctl_net_gn.c

diff --git a/include/linux/gn.h b/include/linux/gn.h
new file mode 100644
index 000000000000..5940e3d85cae
--- /dev/null
+++ b/include/linux/gn.h
@@ -0,0 +1,363 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef __LINUX_GN_H__
+#define __LINUX_GN_H__
+
+#include <net/sock.h>
+#include <uapi/linux/gn.h>
+#include <linux/types.h>
+#include <linux/atomic.h>
+#include <linux/bitfield.h>
+#include <asm/byteorder.h>
+#include <linux/etherdevice.h>
+
+#define GN_MAX_HEADER_SZ 88
+#define GN_MAXSZ (ETH_DATA_LEN - GN_MAX_HEADER_SZ)
+#define GN_MAXHOPS 255 /* 8 bits of hop counter */
+
+#define GN_VERSION 1
+#define GN_BROADCAST_ADDR 0xffffffffffffULL
+/* itsGnDefaultHopLimit: Default hop limit (for BH RHL/CH MHL) */
+#define DEFAULT_HOP_LIMIT 10
+
+/* UNIX timestamp in ms for the ETSI ITS Epoch (2004-01-01 00:00:00 TAI).
+ * 1072915200000 ms (2004-01-01 UTC) + 32000 ms (32 leap seconds).
+ */
+#define GN_EPOCH_UNIX_MS 1072915232000LLU
+
+/* time until location table entry becomes invalid */
+#define GN_LOC_TE_LIFETIME 20000
+/* time between two beacons */
+#define GN_BEACON_RETRANSMIT_TIME 3000
+/* time between retransmits of an unanswered location service request */
+#define GN_LS_RETRANSMIT_TIME 1000
+
+/* sizes in KiB */
+#define GN_UC_BUF_SIZE 256
+#define GN_BC_BUF_SIZE 1024
+#define GN_CBF_BUF_SIZE 256
+
+#define GN_UC_BUF 0
+#define GN_BC_BUF 1
+#define GN_CBF_BUF 2
+
+#define BH_NH_ANY 0
+#define BH_NH_COMMON_HEADER 1
+#define BH_NH_SECURED_PACKET 2
+
+#define GN_LT_BASE_50MS  (0 << 6)
+#define GN_LT_BASE_1S    (1 << 6)
+#define GN_LT_BASE_10S   (2 << 6)
+#define GN_LT_BASE_100S  (3 << 6)
+#define GN_ENCODE_LT(base, mult) ((base) | ((mult) & 0x3f))
+
+/* itsGnDefaultPacketLifetime: Default packet lifetime encoded as 60s (Base=1s, Mult=60) */
+#define BH_LT_DEFAULT GN_ENCODE_LT(GN_LT_BASE_1S, 60)
+
+#define CH_NH_ANY 0
+#define CH_NH_BTPA 1
+#define CH_NH_BTPB 2
+#define CH_NH_IPV6 3
+
+#define CH_FLAG_MOBILE (1 << 7)
+
+/* itsGnDefaultTrafficClass: Default traffic class */
+#define CH_TC_DEFAULT 0
+
+#define CH_HT_BEACON 1
+#define CH_HT_GUC 2
+#define CH_HT_GAC 3
+#define CH_HT_GBC 4
+#define CH_HT_TSB 5
+#define CH_HT_LS 6
+
+#define CH_HST_GABC_CIRCLE 0
+#define CH_HST_GABC_RECT 1
+#define CH_HST_GABC_ELIP 2
+
+#define CH_HST_TSB_SINGLE_HOP 0
+#define CH_HST_TSB_MULTI_HOP 1
+
+#define CH_HST_LS_REQUEST 0
+#define CH_HST_LS_REPLY 1
+
+#define CH_HST_UNSPECIFIED 0
+#define CH_HST_BEACON 1
+
+#define CH_PL_EMPTY 0L
+#define BEACON_MHL 1
+
+#define GN_BASE_HEADER_SIZE \
+	(sizeof(struct gn_basic_header) + sizeof(struct gn_common_header))
+#define GN_SET_BTP(skb, btp_h, extended_header_type)                         \
+	do {                                                                 \
+		skb_set_transport_header(                                    \
+			skb,                                                 \
+			GN_BASE_HEADER_SIZE + sizeof(extended_header_type)); \
+		btp_h = (struct btp_header *)skb_transport_header(skb);      \
+	} while (0)
+
+#define GN_GUC_HLEN sizeof(struct gn_guc_header)
+#define GN_GXC_HLEN sizeof(struct gn_gxc_header)
+#define GN_SHB_HLEN sizeof(struct gn_shb_header)
+#define GN_TSB_HLEN sizeof(struct gn_tsb_header)
+#define GN_BEACON_HLEN sizeof(struct gn_beacon_header)
+#define GN_LS_REQUEST_HLEN sizeof(struct gn_ls_request_header)
+#define GN_LS_REPLY_HLEN sizeof(struct gn_ls_reply_header)
+#define BTP_HLEN sizeof(struct btp_header)
+
+/**
+ *	struct gn_iface - GeoNetworking interface
+ *	@dev - Network device associated with this interface
+ *	@address - Our address
+ *	@local_sn - Current sequence number
+ */
+struct gn_iface {
+	struct net_device	*dev;
+	gn_address_t	address;
+	struct gn_position pos;
+	atomic_t local_sn;
+	struct hlist_node hnode;
+	struct rcu_head rcu;
+};
+
+struct gn_sock {
+	/* struct sock has to be the first member of gn_sock */
+	struct sock	sk;
+	struct gn_scope scope;
+	gn_address_t src_addr;
+	gn_address_t dst_addr;
+	u16 src_port;
+	u16 dst_port;
+	u8 protocol;
+};
+
+static inline struct gn_sock *gn_sk(struct sock *sk)
+{
+	return (struct gn_sock *)sk;
+}
+
+struct btp_header {
+	__be16 dst_port;
+	__be16 src_port;
+} __packed;
+
+enum ITS_TYPE {
+	UNKNOWN,
+	PEDESTRIAN,
+	CYCLIST,
+	MOPED,
+	MOTORCYCLE,
+	PASSENGER_CAR,
+	BUS,
+	LIGHT_TRUCK,
+	HEAVY_TRUCK,
+	TAILER,
+	SPECIAL_VEHICLE,
+	TRAM,
+	ROAD_SIDE_UNIT
+};
+
+#define GN_ADDRESS_M BIT(63)
+#define GN_ADDRESS_ST GENMASK(62, 58)
+#define GN_ADDRESS_RES GENMASK(57, 48)
+#define GN_ADDRESS_MID GENMASK(47, 0)
+
+typedef __be16 gn_spai_t;
+
+#define GN_LPV_PAI BIT(15)
+#define GN_LPV_S GENMASK(14, 0)
+
+struct gn_lpv {
+	gn_address_t addr;
+	__be32 tst;
+	__be32 lat;
+	__be32 lon;
+	gn_spai_t spai;
+	__be16 h; //signed
+} __packed;
+
+struct gn_spv {
+	gn_address_t addr;
+	__be32 tst;
+	__be32 lat; //short
+	__be32 lon;
+} __packed;
+
+/*version: Identifies the version of the GeoNetworking protocol
+ *
+ *nh: Identifies the type of header immediately following the GeoNetworking
+ *    Basic Header
+ *reserved: Reserved. Set to 0
+ *
+ *lt: Lifetime field.
+ *Indicates the maximum tolerable time a packet may be buffered until it
+ *reaches its destination Bit 0 to Bit 5:
+ *lt_sub:
+ *0) 50 ms
+ *1) 1s
+ *2) 10 s
+ *3) 100 s
+ *4) 600 s
+ *5) 1000s
+ *
+ *Bit 6 to Bit 11:
+ *lt_mul: Multiplier for lt_sub, between 0 and 63
+ *
+ *rhl: Remaining hop limit. Set to the maximum hop limit (mhl) initially and
+ *decremented by 1 at each hop.
+ *The packet is dropped when rhl reaches 0
+ */
+struct gn_basic_header {
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+	__u8 nh : 4;
+	__u8 version : 4;
+#elif defined(__BIG_ENDIAN_BITFIELD)
+	__u8 version : 4;
+	__u8 nh : 4;
+#else
+#error "please fix asm/byteorder.h"
+#endif
+	__u8 reserved;
+	__u8 lt;
+	__u8 rhl;
+} __packed;
+
+struct gn_common_header {
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+	__u8 reserved : 4;
+	__u8 nh : 4;
+	__u8 hst : 4;
+	__u8 ht : 4;
+#elif defined(__BIG_ENDIAN_BITFIELD)
+	__u8 nh : 4;
+	__u8 reserved : 4;
+	__u8 ht : 4;
+	__u8 hst : 4;
+#else
+#error "please fix asm/byteorder.h"
+#endif
+
+	__u8 tc;
+	__u8 flags;
+	__be16 pl;
+	__u8 mhl;
+	__u8 reserved2;
+} __packed;
+
+/*there are 6 types of headers:
+ * 1) GUC packet header (clause 9.8.2).
+ * 2) TSB packet header (clause 9.8.3).
+ * 3) SHB packet header (clause 9.8.4).
+ * 4) GBC and GAC packet headers (clause 9.8.5).
+ * 5) BEACON packet header (clause 9.8.6).
+ * 6) LS Request and LS Reply packet headers (clause 9.8.7 and clause 9.8.8).
+ */
+
+struct gn_guc_header {
+	__be16 sn;
+	__be16 reserved;
+	struct gn_lpv sopv;
+	struct gn_spv depv;
+} __packed;
+
+struct gn_tsb_header {
+	__be16 sn;
+	__be16 reserved;
+	struct gn_lpv sopv;
+} __packed;
+
+struct gn_shb_header {
+	struct gn_lpv sopv;
+	__be32 mdd;
+} __packed;
+
+/* GAC and GBC share the same header structure */
+struct gn_gxc_header {
+	__be16 sn;
+	__be16 reserved;
+	struct gn_lpv sopv;
+	__be32 gap_lat; //signed
+	__be32 gap_lon; //signed
+	__be16 da;
+	__be16 db;
+	__be16 angle;
+	__be16 reserved2;
+} __packed;
+
+#define gn_gac_header gn_gxc_header
+#define gn_gbc_header gn_gxc_header
+
+struct gn_beacon_header {
+	struct gn_lpv sopv;
+} __packed;
+
+struct gn_ls_request_header {
+	__be16 sn;
+	__be16 reserved;
+	struct gn_lpv sopv;
+	gn_address_t addr;
+} __packed;
+
+//same structure as gn_guc_header, left in for abstraction
+struct gn_ls_reply_header {
+	__be16 sn;
+	__be16 reserved;
+	struct gn_lpv sopv;
+	struct gn_spv depv;
+} __packed;
+
+struct gn_header {
+	struct gn_basic_header gb_h;
+	struct gn_common_header gc_h;
+	union {
+		struct gn_guc_header guc_h;
+		union {
+			struct gn_gxc_header gac_h;
+			struct gn_gxc_header gbc_h;
+		};
+		struct gn_tsb_header tsb_h;
+		struct gn_shb_header shb_h;
+		struct gn_beacon_header beacon_h;
+		struct gn_ls_request_header ls_request_h;
+		struct gn_ls_reply_header ls_reply_h;
+		__be16 sn;
+	};
+} __packed;
+
+/* Inter module exports */
+
+u32 gn_tai_to_gn(ktime_t tai_time);
+u32 gn_timestamp_now(void);
+
+void gn_fill_sopv(struct gn_iface *gnif, struct gn_lpv *sopv,
+		  gn_address_t addr);
+
+extern struct hlist_head gn_sockets;
+extern rwlock_t gn_sockets_lock;
+
+extern struct hlist_head gn_interfaces;
+extern spinlock_t gn_interfaces_lock;
+
+struct gn_iface *gn_if_add_device(struct net_device *dev,
+				  struct sockaddr_gn *sa);
+void gn_if_drop_device(struct net_device *dev);
+int gn_validate_pos(struct gn_position *pos);
+
+int gn_netlink_init(void);
+void gn_netlink_exit(void);
+
+#ifdef CONFIG_SYSCTL
+extern int gn_register_sysctl(void);
+extern void gn_unregister_sysctl(void);
+#else
+static inline int gn_register_sysctl(void)
+{
+	return 0;
+}
+static inline void gn_unregister_sysctl(void)
+{
+}
+#endif
+
+#endif /* __LINUX_GN_H__ */
diff --git a/include/linux/gn_routing.h b/include/linux/gn_routing.h
new file mode 100644
index 000000000000..64ccc80f5a90
--- /dev/null
+++ b/include/linux/gn_routing.h
@@ -0,0 +1,68 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef __LINUX_GN_ROUTING_H__
+#define __LINUX_GN_ROUTING_H__
+
+#include <linux/gn.h>
+#include <linux/types.h>
+#include <linux/if_ether.h>
+
+#define GN_MAX_PDR_EMA_BETA 90
+#define GN_MAX_PDR 100
+#define GN_DPL_SIZE 8
+#define GN_LSB_SIZE 16	// this is the number of entries per entry (has to be a power of 2 atm)
+			// ETSI sets a maximum of 1024 octets/bytes, which is not trivial to maintain
+
+#define GN_NON_AREA_FORWARDING_UNSPECIFIED	0
+#define GN_NON_AREA_FORWARDING_GREEDY		1
+#define GN_NON_AREA_FORWARDING_CBF		2
+#define GN_NON_AREA_FORWARDING GN_NON_AREA_FORWARDING_GREEDY
+
+#define GN_AREA_FORWARDING_UNSPECIFIED	0
+#define GN_AREA_FORWARDING_SIMPLE	1
+#define GN_AREA_FORWARDING_CBF		2
+#define GN_AREA_FORWARDING_ADVANCED	3
+#define GN_AREA_FORWARDING GN_AREA_FORWARDING_SIMPLE
+
+#define GN_QUEUE_DIRECT		0
+#define GN_QUEUE_LS_PENDING	1
+#define GN_QUEUE_LS_STALE	2
+#define GN_QUEUE_ERROR		3
+
+#define GN_FORWARD_BROADCAST	0
+#define GN_FORWARD_NEXT_HOP	1
+#define GN_FORWARD_BUFFER	2
+#define GN_FORWARD_DISCARD	3
+
+struct gn_dpd_buf {
+	u16 sn[GN_DPL_SIZE];
+	u8 head;
+};
+
+struct loc_te {
+	gn_address_t addr;
+	u8 ll_address[ETH_ALEN];
+	struct gn_lpv pv;
+	struct gn_dpd_buf dpl;
+	struct sk_buff_head lsb;
+	u32 tst_addr;
+	u32 pdr;
+	struct hlist_node hnode;
+	u8 ls_pending: 1;
+	u8 is_neighbour: 1;
+};
+
+s64 gn_F(struct gn_coord self, struct gn_geo_scope scope);
+int gn_gxc_forward(struct gn_iface *gnif, s64 f, u8 *addr, struct gn_lpv *depv);
+
+struct gn_iface *gn_find_interface(gn_address_t addr);
+struct gn_iface *gn_find_interface_by_dev(struct net_device *dev);
+int gn_query_ll_address(gn_address_t addr, u8 *ll_address);
+int gn_query_ll_nexthop(struct gn_iface *gnif, gn_address_t query_addr, u8 *ll_address);
+int gn_fill_depv(struct gn_spv *depv, gn_address_t dest_addr);
+int gn_ls_queue(gn_address_t dest_addr, struct sk_buff *skb);
+void gn_ls_flush(gn_address_t dest_addr);
+int gn_update_location_table(struct gn_lpv *pv, bool make_neighbour, const u8 *ll_address, const __be16 *sn);
+void gn_routing_exit(void);
+
+#endif // __LINUX_GN_ROUTING_H__
diff --git a/include/linux/socket.h b/include/linux/socket.h
index 2a8d7b14f1d1..17d8c466f120 100644
--- a/include/linux/socket.h
+++ b/include/linux/socket.h
@@ -254,8 +254,8 @@ struct ucred {
 #define AF_MCTP		45	/* Management component
 				 * transport protocol
 				 */
-
-#define AF_MAX		46	/* For now.. */
+#define AF_GN		46	/* ETSI ITS GeoNetworking */
+#define AF_MAX		47	/* For now.. */
 
 /* Protocol families, same as address families. */
 #define PF_UNSPEC	AF_UNSPEC
@@ -306,6 +306,7 @@ struct ucred {
 #define PF_SMC		AF_SMC
 #define PF_XDP		AF_XDP
 #define PF_MCTP		AF_MCTP
+#define PF_GN		AF_GN
 #define PF_MAX		AF_MAX
 
 /* Maximum queue length specifiable by listen.  */
@@ -400,6 +401,7 @@ struct ucred {
 #define SOL_MCTP	285
 #define SOL_SMC		286
 #define SOL_VSOCK	287
+#define SOL_GN		288
 
 /* IPX options */
 #define IPX_TYPE	1
diff --git a/include/uapi/linux/gn.h b/include/uapi/linux/gn.h
new file mode 100644
index 000000000000..96cafbda54de
--- /dev/null
+++ b/include/uapi/linux/gn.h
@@ -0,0 +1,84 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+
+#ifndef _UAPI__LINUX_GN_H__
+#define _UAPI__LINUX_GN_H__
+
+#include <linux/types.h>
+#include <asm/byteorder.h>
+#include <linux/socket.h>
+
+#include <linux/time_types.h>
+
+/*
+ * GeoNetworking structures
+ *
+ */
+#define GNPORT_ANY	0
+#define GNPORT_FIRST	1
+#define GNPORT_RESERVED	3000
+#define GNPORT_LAST	65535
+
+#define GNADDR_BROADCAST 0x0000FFFFFFFFFFFFLLU
+
+/* Valid protocols */
+#define GN_PROTO_ANY	0
+#define GN_PROTO_BTP_A	1
+#define GN_PROTO_BTP_B	2
+#define GN_PROTO_INET6	3
+#define GN_PROTO_MAX	GN_PROTO_INET6
+
+/* protocol-specific ioctls */
+#define SIOCGNSPOSITION			(SIOCPROTOPRIVATE + 0)
+
+/* gn_position flags */
+#define GN_POSITION_MOBILE		(1 << 0)
+
+/* {get,set}sockopt options */
+#define GN_SCOPE 1
+
+#define GN_SCOPE_UNSPECIFIED		0
+#define GN_SCOPE_TOPOLOGICAL		1
+#define GN_SCOPE_GEOGRAPHICAL		2
+#define GN_SCOPE_GEOGRAPHICAL_ANYCAST	3
+#define GN_SCOPE_MAX			GN_SCOPE_GEOGRAPHICAL_ANYCAST
+
+#define GN_SHAPE_UNSPECIFIED	0
+#define GN_SHAPE_CIRCLE		1
+#define GN_SHAPE_ELLIPSE	2
+#define GN_SHAPE_RECTANGLE	3
+
+typedef __be64 gn_address_t;
+
+struct gn_coord {
+	__s32 lat;
+	__s32 lon;
+};
+
+struct gn_geo_scope {
+	struct gn_coord coord;
+	__u16 angle;
+	__u16 a, b;
+	__u8 shape;
+};
+
+struct gn_scope {
+	__u8 scope_type;
+	union {
+		__u8 topo_hops;
+		struct gn_geo_scope geo_scope;
+	};
+};
+
+struct gn_position {
+	struct __kernel_timespec tst;
+	struct gn_coord coord;
+	__u8 flags;
+};
+
+struct sockaddr_gn {
+	__kernel_sa_family_t sgn_family;
+	gn_address_t sgn_addr;
+	__u16 sgn_port;
+} __packed;
+
+#endif /* _UAPI__LINUX_GN_H__ */
diff --git a/include/uapi/linux/if_ether.h b/include/uapi/linux/if_ether.h
index 1ffac52c39df..e7b0baa0d15b 100644
--- a/include/uapi/linux/if_ether.h
+++ b/include/uapi/linux/if_ether.h
@@ -113,6 +113,7 @@
 #define ETH_P_FIP	0x8914		/* FCoE Initialization Protocol */
 #define ETH_P_80221	0x8917		/* IEEE 802.21 Media Independent Handover Protocol */
 #define ETH_P_HSR	0x892F		/* IEC 62439-3 HSRv1	*/
+#define ETH_P_GN	0x8947		/* ETSI-ITS Network Header */
 #define ETH_P_NSH	0x894F		/* Network Service Header */
 #define ETH_P_LOOPBACK	0x9000		/* Ethernet loopback packet, per IEEE 802.3 */
 #define ETH_P_QINQ1	0x9100		/* deprecated QinQ VLAN [ NOT AN OFFICIALLY REGISTERED ID ] */
diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h
index 43cecca49f01..6fe5276b1998 100644
--- a/include/uapi/linux/if_link.h
+++ b/include/uapi/linux/if_link.h
@@ -2074,4 +2074,14 @@ enum {
 
 #define IFLA_OVPN_MAX	(__IFLA_OVPN_MAX - 1)
 
+/* GN section */
+
+enum {
+	IFLA_GN_UNSPEC,
+	IFLA_GN_POSITION,
+	__IFLA_GN_MAX,
+};
+
+#define IFLA_GN_MAX (__IFLA_GN_MAX - 1)
+
 #endif /* _UAPI_LINUX_IF_LINK_H */
diff --git a/net/Kconfig b/net/Kconfig
index e38477393551..b49b1e04b0da 100644
--- a/net/Kconfig
+++ b/net/Kconfig
@@ -26,6 +26,8 @@ menuconfig NET
 
 if NET
 
+source "net/gn/Kconfig"
+
 config WANT_COMPAT_NETLINK_MESSAGES
 	bool
 	help
diff --git a/net/Makefile b/net/Makefile
index 5b2dd7f07a85..bd3be868b2d8 100644
--- a/net/Makefile
+++ b/net/Makefile
@@ -75,3 +75,4 @@ obj-$(CONFIG_MPTCP)		+= mptcp/
 obj-$(CONFIG_MCTP)		+= mctp/
 obj-$(CONFIG_NET_HANDSHAKE)	+= handshake/
 obj-$(CONFIG_NET_SHAPER)	+= shaper/
+obj-$(CONFIG_GN)		+= gn/
diff --git a/net/gn/Kconfig b/net/gn/Kconfig
new file mode 100644
index 000000000000..4857e43f02c5
--- /dev/null
+++ b/net/gn/Kconfig
@@ -0,0 +1,4 @@
+config GN
+    tristate "Enable GeoNetworking support"
+    default y
+    select LLC
diff --git a/net/gn/Makefile b/net/gn/Makefile
new file mode 100644
index 000000000000..d8452508a162
--- /dev/null
+++ b/net/gn/Makefile
@@ -0,0 +1,7 @@
+#
+# Makefile for the Linux GeoNetworking layer.
+#
+
+obj-$(CONFIG_GN) += gn.o
+gn-y			:= gn_prot.o gn_routing.o gn_netlink.o
+gn-$(CONFIG_SYSCTL)	+= sysctl_net_gn.o
diff --git a/net/gn/gn_netlink.c b/net/gn/gn_netlink.c
new file mode 100644
index 000000000000..96cb89ea5c42
--- /dev/null
+++ b/net/gn/gn_netlink.c
@@ -0,0 +1,266 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GeoNetworking Netlink interface
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <linux/if_link.h>
+#include <linux/if_addr.h>
+#include <linux/if_arp.h>
+#include <linux/slab.h>
+#include <linux/rculist.h>
+#include <linux/gn.h>
+#include <linux/gn_routing.h>
+#include <net/sock.h>
+
+static const struct nla_policy ifa_gn_policy[IFA_MAX + 1] = {
+	[IFA_ADDRESS]		= { .type = NLA_U64 },
+	[IFA_LOCAL]		= { .type = NLA_U64 },
+};
+
+static int gn_rtm_newaddr(struct sk_buff *skb, struct nlmsghdr *nlh,
+			  struct netlink_ext_ack *extack)
+{
+	struct net *net = sock_net(skb->sk);
+	struct nlattr *tb[IFA_MAX + 1];
+	struct net_device *dev;
+	struct sockaddr_gn sa;
+	struct gn_iface *gnif;
+	struct ifaddrmsg *ifm;
+	gn_address_t *addr;
+	int rc;
+
+	rc = nlmsg_parse(nlh, sizeof(*ifm), tb, IFA_MAX, ifa_gn_policy, extack);
+	if (rc < 0)
+		return rc;
+
+	ifm = nlmsg_data(nlh);
+	if (ifm->ifa_family != AF_GN)
+		return -EPFNOSUPPORT;
+
+	if (tb[IFA_LOCAL])
+		addr = nla_data(tb[IFA_LOCAL]);
+	else if (tb[IFA_ADDRESS])
+		addr = nla_data(tb[IFA_ADDRESS]);
+	else
+		return -EINVAL;
+
+	dev = __dev_get_by_index(net, ifm->ifa_index);
+	if (!dev)
+		return -ENODEV;
+
+	if (dev->type != ARPHRD_ETHER)
+		return -EINVAL;
+
+	if (gn_find_interface_by_dev(dev))
+		return -EEXIST;
+
+	memset(&sa, 0, sizeof(sa));
+	sa.sgn_family = AF_GN;
+	sa.sgn_addr = *addr;
+	sa.sgn_port = GNPORT_ANY;
+
+	gnif = gn_if_add_device(dev, &sa);
+	if (!gnif)
+		return -ENOMEM;
+
+	return 0;
+}
+
+static int gn_rtm_deladdr(struct sk_buff *skb, struct nlmsghdr *nlh,
+			  struct netlink_ext_ack *extack)
+{
+	struct net *net = sock_net(skb->sk);
+	struct nlattr *tb[IFA_MAX + 1];
+	struct net_device *dev;
+	struct ifaddrmsg *ifm;
+	int rc;
+
+	rc = nlmsg_parse(nlh, sizeof(*ifm), tb, IFA_MAX, ifa_gn_policy, extack);
+	if (rc < 0)
+		return rc;
+
+	ifm = nlmsg_data(nlh);
+	if (ifm->ifa_family != AF_GN)
+		return -EPFNOSUPPORT;
+
+	dev = __dev_get_by_index(net, ifm->ifa_index);
+	if (!dev)
+		return -ENODEV;
+
+	if (!gn_find_interface_by_dev(dev))
+		return -EADDRNOTAVAIL;
+
+	gn_if_drop_device(dev);
+	return 0;
+}
+
+static int gn_fill_addrinfo(struct sk_buff *skb, struct gn_iface *gnif,
+			    int msg_type, u32 portid, u32 seq, int flag)
+{
+	struct ifaddrmsg *hdr;
+	struct nlmsghdr *nlh;
+
+	nlh = nlmsg_put(skb, portid, seq, msg_type, sizeof(*hdr), flag);
+	if (!nlh)
+		return -EMSGSIZE;
+
+	hdr = nlmsg_data(nlh);
+	memset(hdr, 0, sizeof(*hdr));
+	hdr->ifa_family = AF_GN;
+	hdr->ifa_prefixlen = 0;
+	hdr->ifa_flags = 0;
+	hdr->ifa_scope = 0;
+	hdr->ifa_index = gnif->dev->ifindex;
+
+	if (nla_put_u64_64bit(skb, IFA_LOCAL, gnif->address, IFA_UNSPEC) ||
+	    nla_put_u64_64bit(skb, IFA_ADDRESS, gnif->address, IFA_UNSPEC)) {
+		nlmsg_cancel(skb, nlh);
+		return -EMSGSIZE;
+	}
+
+	nlmsg_end(skb, nlh);
+	return 0;
+}
+
+static int gn_dump_addrinfo(struct sk_buff *skb, struct netlink_callback *cb)
+{
+	struct net *net = sock_net(skb->sk);
+	struct ifaddrmsg *hdr;
+	struct gn_iface *gnif;
+	int ifindex = 0;
+	int idx = 0;
+	int s_idx = cb->args[0];
+	int rc = 0;
+
+	hdr = nlmsg_payload(cb->nlh, sizeof(*hdr));
+	if (hdr)
+		ifindex = hdr->ifa_index;
+
+	rcu_read_lock();
+	hlist_for_each_entry_rcu(gnif, &gn_interfaces, hnode) {
+		if (!net_eq(dev_net(gnif->dev), net))
+			continue;
+		if (ifindex && ifindex != gnif->dev->ifindex)
+			continue;
+		if (idx < s_idx) {
+			idx++;
+			continue;
+		}
+		rc = gn_fill_addrinfo(skb, gnif, RTM_NEWADDR,
+				      NETLINK_CB(cb->skb).portid,
+				      cb->nlh->nlmsg_seq, NLM_F_MULTI);
+		if (rc < 0)
+			break;
+		idx++;
+	}
+	rcu_read_unlock();
+
+	cb->args[0] = idx;
+	return skb->len;
+}
+
+static const struct nla_policy ifla_gn_policy[IFLA_GN_MAX + 1] = {
+	[IFLA_GN_POSITION]	= NLA_POLICY_EXACT_LEN(sizeof(struct gn_position)),
+};
+
+static int gn_fill_link_af(struct sk_buff *skb, const struct net_device *dev,
+			   u32 ext_filter_mask)
+{
+	struct gn_iface *gnif;
+
+	gnif = gn_find_interface_by_dev((struct net_device *)dev);
+	if (!gnif)
+		return -ENODATA;
+
+	if (nla_put(skb, IFLA_GN_POSITION, sizeof(gnif->pos), &gnif->pos))
+		return -EMSGSIZE;
+
+	return 0;
+}
+
+static size_t gn_get_link_af_size(const struct net_device *dev,
+				  u32 ext_filter_mask)
+{
+	struct gn_iface *gnif;
+
+	gnif = gn_find_interface_by_dev((struct net_device *)dev);
+	if (!gnif)
+		return 0;
+
+	return nla_total_size(sizeof(struct gn_position));
+}
+
+static int gn_set_link_af(struct net_device *dev, const struct nlattr *attr,
+			  struct netlink_ext_ack *extack)
+{
+	struct nlattr *tb[IFLA_GN_MAX + 1];
+	struct gn_position pos;
+	struct gn_iface *gnif;
+	int rc;
+
+	rc = nla_parse_nested(tb, IFLA_GN_MAX, attr, ifla_gn_policy, extack);
+	if (rc < 0)
+		return rc;
+
+	if (!tb[IFLA_GN_POSITION])
+		return 0;
+
+	if (!capable(CAP_NET_ADMIN))
+		return -EPERM;
+
+	if (nla_len(tb[IFLA_GN_POSITION]) < sizeof(struct gn_position))
+		return -EINVAL;
+
+	memcpy(&pos, nla_data(tb[IFLA_GN_POSITION]), sizeof(pos));
+	rc = gn_validate_pos(&pos);
+	if (rc < 0)
+		return rc;
+
+	gnif = gn_find_interface_by_dev(dev);
+	if (!gnif)
+		return -EADDRNOTAVAIL;
+
+	memcpy(&gnif->pos, &pos, sizeof(pos));
+	return 0;
+}
+
+static struct rtnl_af_ops gn_af_ops __read_mostly = {
+	.family			= PF_GN,
+	.fill_link_af		= gn_fill_link_af,
+	.get_link_af_size	= gn_get_link_af_size,
+	.set_link_af		= gn_set_link_af,
+};
+
+static const struct rtnl_msg_handler gn_rtnl_msg_handlers[] = {
+	{ .owner = THIS_MODULE, .protocol = PF_GN, .msgtype = RTM_NEWADDR,
+	  .doit = gn_rtm_newaddr },
+	{ .owner = THIS_MODULE, .protocol = PF_GN, .msgtype = RTM_DELADDR,
+	  .doit = gn_rtm_deladdr },
+	{ .owner = THIS_MODULE, .protocol = PF_GN, .msgtype = RTM_GETADDR,
+	  .dumpit = gn_dump_addrinfo },
+};
+
+int __init gn_netlink_init(void)
+{
+	int err;
+
+	err = rtnl_af_register(&gn_af_ops);
+	if (err)
+		return err;
+
+	err = rtnl_register_many(gn_rtnl_msg_handlers);
+	if (err)
+		rtnl_af_unregister(&gn_af_ops);
+
+	return err;
+}
+
+void gn_netlink_exit(void)
+{
+	rtnl_unregister_many(gn_rtnl_msg_handlers);
+	rtnl_af_unregister(&gn_af_ops);
+}
diff --git a/net/gn/gn_prot.c b/net/gn/gn_prot.c
new file mode 100644
index 000000000000..58095c4dc475
--- /dev/null
+++ b/net/gn/gn_prot.c
@@ -0,0 +1,1908 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * GeoNetworking
+ */
+
+#include <asm-generic/errno-base.h>
+#define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__
+#include <linux/if_arp.h>
+#include <linux/slab.h>
+#include <linux/termios.h>
+#include <net/sock.h>
+#include <net/datalink.h>
+#include <net/psnap.h>
+#include <linux/gn.h>
+#include <linux/gn_routing.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/export.h>
+#include <linux/etherdevice.h>
+#include <linux/timer.h>
+#include <linux/time.h>
+#include <linux/random.h>
+#include <linux/atomic.h>
+#include <linux/limits.h>
+#include <linux/if_ether.h>
+#include <linux/bug.h>
+
+struct datalink_proto *gn_dl;
+static const struct proto_ops gn_dgram_ops;
+static struct timer_list gn_beacon_timer;
+
+/* Handlers for the socket list. */
+
+DEFINE_RWLOCK(gn_sockets_lock);
+HLIST_HEAD(gn_sockets);
+
+static void __gn_insert_socket(struct sock *sk)
+{
+	sk_add_node(sk, &gn_sockets);
+}
+
+static void gn_remove_socket(struct sock *sk)
+{
+	write_lock_bh(&gn_sockets_lock);
+	sk_del_node_init(sk);
+	write_unlock_bh(&gn_sockets_lock);
+}
+
+#define from_timer(var, callback_timer, timer_fieldname) \
+	container_of(callback_timer, typeof(*(var)), timer_fieldname)
+
+static void gn_destroy_timer(struct timer_list *t)
+{
+	struct sock *sk = from_timer(sk, t, sk_timer);
+
+	if (sk_has_allocations(sk)) {
+		sk->sk_timer.expires = jiffies + SOCK_DESTROY_TIME;
+		add_timer(&sk->sk_timer);
+	} else {
+		sock_put(sk);
+	}
+}
+
+static void gn_destroy_socket(struct sock *sk)
+{
+	gn_remove_socket(sk);
+	skb_queue_purge(&sk->sk_receive_queue);
+
+	if (sk_has_allocations(sk)) {
+		timer_setup(&sk->sk_timer, gn_destroy_timer, 0);
+		sk->sk_timer.expires = jiffies + SOCK_DESTROY_TIME;
+		add_timer(&sk->sk_timer);
+	} else {
+		sock_put(sk);
+	}
+}
+
+/* Handling for system calls applied via the various interfaces to a
+ * GeoNetworking socket object.
+ */
+
+static struct proto gn_proto = {
+	.name = "GN",
+	.owner = THIS_MODULE,
+	.obj_size = sizeof(struct gn_sock),
+};
+
+HLIST_HEAD(gn_interfaces);
+DEFINE_SPINLOCK(gn_interfaces_lock);
+
+static void gn_activate_beacon(void);
+
+/**
+ * gn_iface - add device to the interface the socketaddress is bound to
+ */
+struct gn_iface *gn_if_add_device(struct net_device *dev,
+				  struct sockaddr_gn *sa)
+{
+	struct gn_iface *new_gnif = kzalloc_obj(*new_gnif, GFP_KERNEL);
+	struct gn_iface *gnif;
+	bool was_empty;
+
+	if (!new_gnif)
+		return NULL;
+
+	new_gnif->address = sa->sgn_addr;
+	new_gnif->dev = dev;
+
+	pr_info("Add interface %s with address %llx", dev->name,
+		new_gnif->address);
+
+	spin_lock_bh(&gn_interfaces_lock);
+	hlist_for_each_entry_rcu(gnif, &gn_interfaces, hnode) {
+		if (gnif->dev == dev) {
+			// Replace existing interface address
+			atomic_set(&new_gnif->local_sn,
+				   atomic_read(&gnif->local_sn));
+			memcpy(&new_gnif->pos, &gnif->pos, sizeof(gnif->pos));
+			hlist_replace_rcu(&gnif->hnode, &new_gnif->hnode);
+			spin_unlock_bh(&gn_interfaces_lock);
+			kfree_rcu(gnif, rcu);
+			return new_gnif;
+		}
+	}
+	was_empty = hlist_empty(&gn_interfaces);
+
+	// No existing interface address
+	hlist_add_head_rcu(&new_gnif->hnode, &gn_interfaces);
+	spin_unlock_bh(&gn_interfaces_lock);
+
+	if (was_empty)
+		gn_activate_beacon();
+
+	return new_gnif;
+}
+
+void gn_if_drop_device(struct net_device *dev)
+{
+	struct hlist_node *tmp;
+	struct gn_iface *gnif;
+
+	spin_lock_bh(&gn_interfaces_lock);
+	hlist_for_each_entry_safe(gnif, tmp, &gn_interfaces, hnode) {
+		if (gnif->dev == dev) {
+			hlist_del_rcu(&gnif->hnode);
+			kfree_rcu(gnif, rcu);
+			break;
+		}
+	}
+	spin_unlock_bh(&gn_interfaces_lock);
+}
+
+static void gn_interfaces_clear(void)
+{
+	struct hlist_node *tmp;
+	struct gn_iface *gnif;
+
+	spin_lock_bh(&gn_interfaces_lock);
+	hlist_for_each_entry_safe(gnif, tmp, &gn_interfaces, hnode) {
+		hlist_del_rcu(&gnif->hnode);
+		kfree_rcu(gnif, rcu);
+	}
+	spin_unlock_bh(&gn_interfaces_lock);
+}
+
+/*
+ * find the interface to which the socketaddress is bound
+ */
+struct gn_iface *gn_find_interface(gn_address_t addr)
+{
+	struct gn_iface *gnif;
+	bool found = false;
+
+	rcu_read_lock();
+	hlist_for_each_entry_rcu(gnif, &gn_interfaces, hnode) {
+		if (gnif->address == addr) {
+			found = true;
+			break;
+		}
+	}
+	rcu_read_unlock();
+	return found ? gnif : NULL;
+}
+
+/*
+ * find an interface by the device it belongs to
+ */
+struct gn_iface *gn_find_interface_by_dev(struct net_device *dev)
+{
+	struct gn_iface *gnif;
+	bool found = false;
+
+	rcu_read_lock();
+	hlist_for_each_entry_rcu(gnif, &gn_interfaces, hnode) {
+		if (gnif->dev == dev) {
+			found = true;
+			break;
+		}
+	}
+	rcu_read_unlock();
+	return found ? gnif : NULL;
+}
+
+/*
+ * A device event has occurred. Watch for devices going down and
+ * delete our use of them (iface and route).
+ */
+static int gn_device_event(struct notifier_block *this, unsigned long event,
+			   void *ptr)
+{
+	struct net_device *dev = netdev_notifier_info_to_dev(ptr);
+
+	if (!net_eq(dev_net(dev), &init_net))
+		return NOTIFY_DONE;
+
+	if (dev->type != ARPHRD_ETHER)
+		return NOTIFY_DONE;
+
+	if (event == NETDEV_DOWN || event == NETDEV_UNREGISTER)
+		gn_if_drop_device(dev);
+
+	return NOTIFY_DONE;
+}
+
+/*
+ * Convert TAI timestamp in milliseconds to GN timestamp
+ */
+u32 gn_tai_to_gn(ktime_t tai_time)
+{
+	const ktime_t tai_offset = ms_to_ktime(GN_EPOCH_UNIX_MS);
+
+	WARN_ONCE(ktime_before(tai_time, tai_offset),
+		  "timestamp %lld out of bounds", tai_time);
+	/* truncate timestamp (GN timestamp has 32 bits) */
+	return (u32)ktime_sub(tai_time, tai_offset);
+}
+
+u32 gn_timestamp_now(void)
+{
+	return gn_tai_to_gn(ktime_get_clocktai());
+}
+
+/*
+ * Create a socket. Initialise the socket, blank the addresses
+ * set the state.
+ */
+static int gn_create(struct net *net, struct socket *sock, int protocol,
+		     int kern)
+{
+	struct sock *sk;
+	int rc;
+
+	rc = -EAFNOSUPPORT;
+	if (!net_eq(net, &init_net))
+		goto out;
+
+	rc = -ESOCKTNOSUPPORT;
+	if (sock->type != SOCK_DGRAM)
+		goto out;
+
+	if (protocol < GN_PROTO_ANY || protocol > GN_PROTO_MAX)
+		goto out;
+
+	/* Note: Only BTP/GeoNetworking protocols supported; IPv6 encap not enabled */
+	if (protocol == GN_PROTO_INET6)
+		goto out;
+
+	rc = -ENOMEM;
+	sk = sk_alloc(net, PF_GN, GFP_KERNEL, &gn_proto, kern);
+	if (!sk)
+		goto out;
+	rc = 0;
+	sock->ops = &gn_dgram_ops;
+	sock_init_data(sock, sk);
+	if (protocol == GN_PROTO_ANY)
+		gn_sk(sk)->protocol = GN_PROTO_BTP_A;
+	else
+		gn_sk(sk)->protocol = protocol;
+
+	/* Checksums on by default */
+//	sock_set_flag(sk, SOCK_ZAPPED);
+out:
+	return rc;
+}
+
+/* Free a socket. No work needed */
+static int gn_release(struct socket *sock)
+{
+	struct sock *sk = sock->sk;
+
+	if (sk) {
+		sock_hold(sk);
+		lock_sock(sk);
+
+		sock_orphan(sk);
+		sock->sk = NULL;
+		gn_destroy_socket(sk);
+
+		release_sock(sk);
+		sock_put(sk);
+	}
+	return 0;
+}
+
+static int gn_pick_and_bind_port(struct sock *sk, struct sockaddr_gn *sgn)
+{
+	int retval;
+
+	write_lock_bh(&gn_sockets_lock);
+
+	for (sgn->sgn_port = GNPORT_RESERVED; sgn->sgn_port < GNPORT_LAST;
+	     sgn->sgn_port++) {
+		struct sock *s;
+
+		sk_for_each(s, &gn_sockets) {
+			struct gn_sock *gn = gn_sk(s);
+
+			if (gn->src_port == sgn->sgn_port &&
+			    gn->src_addr == sgn->sgn_addr)
+				goto try_next_port;
+		}
+
+		/* Wheee, it's free, assign and insert. */
+		__gn_insert_socket(sk);
+		gn_sk(sk)->src_port = sgn->sgn_port;
+		retval = 0;
+		goto out;
+
+try_next_port:;
+	}
+
+	retval = -EBUSY;
+out:
+	write_unlock_bh(&gn_sockets_lock);
+	return retval;
+}
+
+static struct sock *gn_find_or_insert_socket(struct sock *sk,
+					     struct sockaddr_gn *sgn)
+{
+	struct gn_sock *gn;
+	struct sock *s;
+
+	write_lock_bh(&gn_sockets_lock);
+	sk_for_each(s, &gn_sockets) {
+		gn = gn_sk(s);
+
+		if (gn->src_port == sgn->sgn_port)
+			goto found;
+	}
+	s = NULL;
+	gn = gn_sk(sk);
+	gn->src_addr = sgn->sgn_addr;
+	gn->src_port = sgn->sgn_port;
+	pr_info("gn: Add socket addr=%llx port=%d", gn->src_addr, gn->src_port);
+	__gn_insert_socket(sk); /* Wheee, it's free, assign and insert. */
+found:
+	write_unlock_bh(&gn_sockets_lock);
+	return s;
+}
+
+static int gn_autobind(struct sock *sock)
+{
+	return -EOPNOTSUPP;
+}
+
+/* Set the address 'our end' of the connection */
+static int gn_bind(struct socket *sock, struct sockaddr_unsized *uaddr,
+		   int addr_len)
+{
+	DECLARE_SOCKADDR(struct sockaddr_gn *, addr, uaddr);
+	struct sock *sk = sock->sk;
+	struct gn_sock *gn;
+	int err;
+
+	gn = gn_sk(sk);
+
+	if (!sock_flag(sk, SOCK_ZAPPED) ||
+	    addr_len != sizeof(struct sockaddr_gn))
+		return -EINVAL;
+
+	if (addr->sgn_family != AF_GN)
+		return -EAFNOSUPPORT;
+
+	lock_sock(sk);
+	if (addr->sgn_addr != 0 && !gn_find_interface(addr->sgn_addr)) {
+		release_sock(sk);
+		return -EADDRNOTAVAIL;
+	}
+	gn->src_addr = addr->sgn_addr;
+
+	if (addr->sgn_port == GNPORT_ANY) {
+		err = gn_pick_and_bind_port(sk, addr);
+
+		if (err < 0)
+			goto out;
+	} else {
+		err = -EADDRINUSE;
+		if (gn_find_or_insert_socket(sk, addr))
+			goto out;
+	}
+	sock_reset_flag(sk, SOCK_ZAPPED);
+	err = 0;
+
+out:
+	release_sock(sk);
+	return err;
+}
+
+/* Set the address we talk to */
+static int gn_connect(struct socket *sock, struct sockaddr_unsized *uaddr,
+		      int addr_len, int flags)
+{
+	struct sock *sk = sock->sk;
+	struct sockaddr_gn *addr;
+	struct gn_sock *gn;
+	int err;
+
+	gn = gn_sk(sk);
+
+	sk->sk_state = TCP_CLOSE;
+	sock->state = SS_UNCONNECTED;
+
+	if (addr_len != sizeof(*addr))
+		return -EINVAL;
+
+	addr = (struct sockaddr_gn *)uaddr;
+
+	if (addr->sgn_family != AF_GN)
+		return -EAFNOSUPPORT;
+
+	lock_sock(sk);
+	err = -EBUSY;
+	if (sock_flag(sk, SOCK_ZAPPED))
+		if (gn_autobind(sk) < 0)
+			goto out;
+
+	/* Note: Route resolution for connected sockets occurs during gn_sendmsg */
+
+	gn->dst_port = addr->sgn_port;
+	gn->dst_addr = addr->sgn_addr;
+
+	sock->state = SS_CONNECTED;
+	sk->sk_state = TCP_ESTABLISHED;
+	err = 0;
+out:
+	release_sock(sk);
+	return err;
+}
+
+static struct sock *gn_search_socket(struct sockaddr_gn *tosgn,
+				     struct gn_iface *gnif)
+{
+	struct gn_sock *gn;
+	struct sock *s;
+
+	read_lock_bh(&gn_sockets_lock);
+	sk_for_each(s, &gn_sockets) {
+		gn = gn_sk(s);
+
+		if (gn->src_port != tosgn->sgn_port)
+			continue;
+		if (!gnif || gn->src_addr == gnif->address) {
+			sock_hold(s);
+			goto out;
+		}
+	}
+	s = NULL;
+out:
+	read_unlock_bh(&gn_sockets_lock);
+	return s;
+}
+
+static u16 gn_if_next_sn(struct gn_iface *gnif)
+{
+	return atomic_inc_return(&gnif->local_sn) % USHRT_MAX;
+}
+
+void gn_fill_sopv(struct gn_iface *gnif, struct gn_lpv *sopv, gn_address_t addr)
+{
+	/* Note: Speed and Heading zeroed until velocity sensors integrated */
+
+	ktime_t tst = ktime_set(gnif->pos.tst.tv_sec, gnif->pos.tst.tv_nsec);
+	// fill empty or invalid timestamp with current timestamp
+	// (must be >= 2004-01-01 ETSI Epoch)
+	if (tst == 0 || ktime_before(tst, ms_to_ktime(GN_EPOCH_UNIX_MS)))
+		sopv->tst = cpu_to_be32(gn_timestamp_now());
+	else
+		sopv->tst = cpu_to_be32(gn_tai_to_gn(tst));
+	sopv->lon = cpu_to_be32(gnif->pos.coord.lon);
+	sopv->lat = cpu_to_be32(gnif->pos.coord.lat);
+	sopv->addr = addr;
+	sopv->spai = cpu_to_be16(FIELD_PREP(GN_LPV_PAI, 0) |
+				 FIELD_PREP(GN_LPV_S, 0));
+	sopv->h = cpu_to_be16(0);
+}
+
+static void gn_fill_bh_ch(struct gn_iface *gnif, struct gn_header *gh,
+			  u8 packet_type, u8 packet_subtype, u8 rhl,
+			  u8 next_header, __be16 payload_size)
+{
+	const u8 mhl = DEFAULT_HOP_LIMIT;
+	/* rhl should never be greater than mhl */
+	if (WARN_ON(rhl > mhl))
+		rhl = DEFAULT_HOP_LIMIT;
+
+	gh->gb_h.version = GN_VERSION;
+	gh->gb_h.nh = BH_NH_COMMON_HEADER;
+	gh->gb_h.reserved = 0;
+	gh->gb_h.lt = BH_LT_DEFAULT;
+	gh->gb_h.rhl = rhl;
+
+	gh->gc_h.nh = next_header;
+	gh->gc_h.reserved = 0;
+	gh->gc_h.reserved2 = 0;
+	gh->gc_h.ht = packet_type;
+	gh->gc_h.hst = packet_subtype;
+	gh->gc_h.tc = CH_TC_DEFAULT;
+	gh->gc_h.pl = payload_size;
+	if (packet_type == CH_HT_BEACON)
+		gh->gc_h.mhl = BEACON_MHL;
+	else
+		gh->gc_h.mhl = DEFAULT_HOP_LIMIT;
+
+	if (gnif->pos.flags & GN_POSITION_MOBILE)
+		gh->gc_h.flags = CH_FLAG_MOBILE;
+	else
+		gh->gc_h.flags = 0;
+}
+
+static void gn_fill_bh_ch_nopayload(struct gn_iface *gnif,
+				    struct gn_header *gn_h, u8 packet_type,
+				    u8 packet_subtype, u8 rhl)
+{
+	gn_fill_bh_ch(gnif, gn_h, packet_type, packet_subtype, rhl, CH_NH_ANY,
+		      0);
+}
+
+static void gn_location_service_req(struct gn_iface *gnif, gn_address_t saddr,
+				    gn_address_t daddr)
+{
+	struct gn_ls_request_header *gls_h;
+	struct gn_common_header *gc_h;
+	struct gn_basic_header *gb_h;
+	struct sk_buff *skb;
+	int size = 0;
+
+	size += gn_dl->header_length;
+	size += gnif->dev->hard_header_len;
+	size += sizeof(struct gn_basic_header);
+	size += sizeof(struct gn_common_header);
+	size += sizeof(struct gn_ls_request_header);
+
+	skb = netdev_alloc_skb(gnif->dev, size);
+	if (!skb)
+		return;
+	skb_reserve(skb, gn_dl->header_length);
+	skb_reserve(skb, gnif->dev->hard_header_len);
+	skb_reserve(skb, sizeof(struct gn_basic_header));
+	skb_reserve(skb, sizeof(struct gn_common_header));
+	skb_reserve(skb, sizeof(struct gn_ls_request_header));
+
+	gls_h = skb_push(skb, sizeof(struct gn_ls_request_header));
+	gc_h = skb_push(skb, sizeof(struct gn_common_header));
+	gb_h = skb_push(skb, sizeof(struct gn_basic_header));
+
+	gn_fill_bh_ch_nopayload(gnif, (struct gn_header *)gb_h, CH_HT_LS,
+				CH_HST_LS_REQUEST, DEFAULT_HOP_LIMIT);
+
+	gls_h->sn = cpu_to_be16(gn_if_next_sn(gnif));
+	gls_h->reserved = 0;
+	gls_h->addr = daddr;
+	gn_fill_sopv(gnif, &gls_h->sopv, saddr);
+
+	gn_dl->request(gn_dl, skb, gnif->dev->broadcast);
+}
+
+static void gn_location_service_reply(struct gn_spv *depv,
+				      struct gn_iface *gnif, gn_address_t addr,
+				      u64 llc)
+{
+	struct gn_ls_reply_header *gls_h;
+	struct gn_common_header *gc_h;
+	struct gn_basic_header *gb_h;
+	struct sk_buff *skb;
+	int size;
+
+	size = 0;
+	size += gn_dl->header_length;
+	size += gnif->dev->hard_header_len;
+	size += sizeof(struct gn_basic_header);
+	size += sizeof(struct gn_common_header);
+	size += sizeof(struct gn_ls_reply_header);
+
+	skb = netdev_alloc_skb(gnif->dev, size);
+	if (!skb)
+		return;
+	skb_reserve(skb, gn_dl->header_length);
+	skb_reserve(skb, gnif->dev->hard_header_len);
+	skb_reserve(skb, sizeof(struct gn_basic_header));
+	skb_reserve(skb, sizeof(struct gn_common_header));
+	skb_reserve(skb, sizeof(struct gn_ls_reply_header));
+
+	gls_h = skb_push(skb, sizeof(struct gn_ls_reply_header));
+	gc_h = skb_push(skb, sizeof(struct gn_common_header));
+	gb_h = skb_push(skb, sizeof(struct gn_basic_header));
+
+	gn_fill_bh_ch_nopayload(gnif, (struct gn_header *)gb_h, CH_HT_LS,
+				CH_HST_LS_REPLY, DEFAULT_HOP_LIMIT);
+
+	gls_h->sn = cpu_to_be16(gn_if_next_sn(gnif));
+	gls_h->reserved = 0;
+	gn_fill_sopv(gnif, &gls_h->sopv, addr);
+	memcpy(&gls_h->depv, depv, sizeof(*depv));
+
+	gn_dl->request(gn_dl, skb, gnif->dev->broadcast);
+}
+
+static int gn_pass_payload_sock(struct sockaddr_gn *tosgn, struct sk_buff *skb)
+{
+	int rc = NET_RX_DROP;
+	struct sock *sock;
+
+	sock = gn_search_socket(tosgn, NULL);
+	if (!sock)
+		return NET_RX_DROP;
+	if (sock_queue_rcv_skb(sock, skb) == 0)
+		rc = NET_RX_SUCCESS;
+	sock_put(sock);
+	return rc;
+}
+
+static int gn_forward_guc_packet(struct sk_buff *skb, gn_address_t dest_addr)
+{
+	struct sk_buff *forward_skb;
+	u8 next_hop_mac[ETH_ALEN];
+	struct gn_header *fwd_gh;
+	struct gn_iface *gnif;
+
+	fwd_gh = (struct gn_header *)skb_network_header(skb);
+	if (fwd_gh->gb_h.rhl <= 0)
+		return -EINVAL;
+
+	gnif = gn_find_interface_by_dev(skb->dev);
+	if (!gnif)
+		return -ENODEV;
+
+	forward_skb = skb_copy(skb, GFP_ATOMIC);
+	if (!forward_skb) {
+		pr_warn("Dropping packet during GUC forwarding\n");
+		return -ENOMEM;
+	}
+
+	fwd_gh = (struct gn_header *)skb_network_header(forward_skb);
+	fwd_gh->gb_h.rhl--;
+
+	if (gn_query_ll_nexthop(gnif, dest_addr, next_hop_mac) == 0)
+		gn_dl->request(gn_dl, forward_skb, next_hop_mac);
+	else
+		gn_dl->request(gn_dl, forward_skb, skb->dev->broadcast);
+
+	return 0;
+}
+
+static int gn_forward_tsb_packet(struct sk_buff *skb)
+{
+	struct sk_buff *forward_skb;
+	struct gn_header *fwd_gh;
+
+	fwd_gh = (struct gn_header *)skb_network_header(skb);
+	if (fwd_gh->gb_h.rhl <= 0)
+		return -EINVAL;
+
+	forward_skb = skb_copy(skb, GFP_ATOMIC);
+	if (!forward_skb) {
+		pr_warn("Dropping packet during TSB forwarding\n");
+		return -ENOMEM;
+	}
+
+	fwd_gh = (struct gn_header *)skb_network_header(forward_skb);
+	fwd_gh->gb_h.rhl--;
+
+	gn_dl->request(gn_dl, forward_skb, skb->dev->broadcast);
+	return 0;
+}
+
+static int gn_process_guc_packet(struct sk_buff *skb)
+{
+	struct btp_header *btp_h;
+	struct sockaddr_gn tosgn;
+	struct gn_iface *gnif;
+	struct gn_header *gh;
+
+	gh = (struct gn_header *)skb_network_header(skb);
+	GN_SET_BTP(skb, btp_h, struct gn_guc_header);
+
+	skb_reset_transport_header(skb);
+
+	tosgn.sgn_family = PF_GN;
+	tosgn.sgn_addr = gh->guc_h.depv.addr;
+	tosgn.sgn_port = be16_to_cpu(btp_h->dst_port);
+
+	if (gn_update_location_table(&gh->guc_h.sopv, false, NULL,
+				     &gh->guc_h.sn))
+		goto drop;
+
+	gnif = gn_find_interface(tosgn.sgn_addr);
+	if (!gnif) {
+		if (gn_forward_guc_packet(skb, tosgn.sgn_addr) == 0) {
+			kfree_skb(skb);
+			return NET_RX_SUCCESS;
+		}
+		goto drop;
+	}
+
+	if (gn_pass_payload_sock(&tosgn, skb) != NET_RX_SUCCESS)
+		goto drop;
+
+	return NET_RX_SUCCESS;
+drop:
+	kfree_skb(skb);
+	return NET_RX_DROP;
+}
+
+static u8 gn_decode_shape(u8 hst)
+{
+	switch (hst) {
+	case CH_HST_GABC_CIRCLE:
+		return GN_SHAPE_CIRCLE;
+	case CH_HST_GABC_RECT:
+		return GN_SHAPE_RECTANGLE;
+	case CH_HST_GABC_ELIP:
+		return GN_SHAPE_ELLIPSE;
+	default:
+		return GN_SHAPE_UNSPECIFIED;
+	};
+}
+
+static u8 gn_encode_shape(u8 shape)
+{
+	switch (shape) {
+	case GN_SHAPE_CIRCLE:
+		return CH_HST_GABC_CIRCLE;
+	case GN_SHAPE_RECTANGLE:
+		return CH_HST_GABC_RECT;
+	case GN_SHAPE_ELLIPSE:
+		return CH_HST_GABC_ELIP;
+	default:
+		return CH_HST_UNSPECIFIED;
+	};
+}
+
+static struct gn_geo_scope gn_decode_geo_scope(struct gn_header *gh)
+{
+	struct gn_geo_scope scope = {
+		.a = be16_to_cpu(gh->gbc_h.da),
+		.b = be16_to_cpu(gh->gbc_h.db),
+		.angle = be16_to_cpu(gh->gbc_h.angle),
+		.coord.lat = be32_to_cpu(gh->gbc_h.gap_lat),
+		.coord.lon = be32_to_cpu(gh->gbc_h.gap_lon),
+		.shape = gn_decode_shape(gh->gc_h.hst),
+	};
+	return scope;
+}
+
+static int gn_process_gxc_packet(struct sk_buff *skb)
+{
+	unsigned long long next_hop_addr;
+	struct gn_geo_scope scope;
+	struct btp_header *btp_h;
+	struct sockaddr_gn tosgn;
+	struct gn_iface *gnif;
+	struct gn_header *gh;
+	bool run_dpd;
+	s64 f_value;
+
+	next_hop_addr = GN_BROADCAST_ADDR;
+
+	gh = (struct gn_header *)skb_network_header(skb);
+	GN_SET_BTP(skb, btp_h, struct gn_gxc_header);
+
+	tosgn.sgn_family = PF_GN;
+	tosgn.sgn_addr = gh->gbc_h.sopv.addr;
+	tosgn.sgn_port = be16_to_cpu(btp_h->dst_port);
+
+	/* Resolve local GeoNetworking interface to check area membership */
+	gnif = gn_find_interface_by_dev(skb->dev);
+	if (!gnif)
+		goto drop;
+
+	scope = gn_decode_geo_scope(gh);
+	if (scope.shape == GN_SHAPE_UNSPECIFIED)
+		goto drop;
+
+	f_value = gn_F(gnif->pos.coord, scope);
+
+	if (f_value >= 0) {
+		// GeoAdhoc router is outside specified area
+		switch (GN_AREA_FORWARDING) {
+		case GN_AREA_FORWARDING_UNSPECIFIED:
+		case GN_AREA_FORWARDING_SIMPLE:
+			run_dpd = true;
+			break;
+		default:
+			run_dpd = false;
+			break;
+		}
+	} else {
+		// GeoAdhoc router is inside specified area
+		switch (GN_NON_AREA_FORWARDING) {
+		case GN_NON_AREA_FORWARDING_UNSPECIFIED:
+		case GN_NON_AREA_FORWARDING_GREEDY:
+			run_dpd = true;
+			break;
+		default:
+			run_dpd = false;
+		}
+	}
+	if (gn_update_location_table(&gh->gbc_h.sopv, false, NULL,
+				     run_dpd ? &gh->gbc_h.sn : NULL))
+		goto drop;
+	// Forwarding
+	if (gh->gb_h.rhl > 0 && ((gh->gc_h.ht == CH_HT_GAC && f_value >= 0) ||
+				 gh->gc_h.ht == CH_HT_GBC)) {
+		struct gn_basic_header *fwd_gb_h;
+		struct sk_buff *forward_skb;
+		u8 dest_addr[ETH_ALEN];
+		int rc;
+
+		forward_skb = skb_copy(skb, GFP_ATOMIC);
+		if (!forward_skb) {
+			pr_debug("dropping packet");
+			goto drop;
+		}
+		fwd_gb_h = (struct gn_basic_header *)skb_network_header(
+			forward_skb);
+		fwd_gb_h->rhl--;
+
+		rc = gn_gxc_forward(gnif, f_value, dest_addr, &gh->gbc_h.sopv);
+		switch (rc) {
+		case GN_FORWARD_NEXT_HOP:
+			gn_dl->request(gn_dl, forward_skb, dest_addr);
+			break;
+		case GN_FORWARD_BROADCAST:
+			gn_dl->request(gn_dl, forward_skb, skb->dev->broadcast);
+			break;
+		case GN_FORWARD_BUFFER:
+			pr_debug("forwarding buffers not implemented");
+			kfree_skb(forward_skb);
+			break;
+		case GN_FORWARD_DISCARD:
+			kfree_skb(forward_skb);
+			break;
+		default:
+			pr_debug("internal error: unexpected return value");
+			kfree_skb(forward_skb);
+			goto drop;
+		}
+	}
+
+	// Local delivery
+	if (f_value >= 0) {
+		if (gn_pass_payload_sock(&tosgn, skb) != NET_RX_SUCCESS)
+			goto drop;
+	} else {
+		kfree_skb(skb);
+	}
+
+	return NET_RX_SUCCESS;
+drop:
+	kfree_skb(skb);
+	return NET_RX_DROP;
+}
+
+static int gn_process_shb_packet(struct sk_buff *skb, const u8 *ll_address)
+{
+	struct btp_header *btp_h;
+	struct sockaddr_gn tosgn;
+	struct gn_header *gh;
+
+	gh = (struct gn_header *)skb_network_header(skb);
+	GN_SET_BTP(skb, btp_h, struct gn_shb_header);
+
+	/* Step 3: Duplicate Address Detection (DAD) check */
+
+	// 4. update PV in the LocTE
+	if (gn_update_location_table(&gh->shb_h.sopv, true, ll_address, NULL))
+		goto drop;
+
+	// 7. pass payload of GN_PDU to the upper protocol unit
+	tosgn.sgn_family = PF_GN;
+	tosgn.sgn_addr = gh->shb_h.sopv.addr;
+	tosgn.sgn_port = be16_to_cpu(btp_h->dst_port);
+
+	if (gn_pass_payload_sock(&tosgn, skb) != NET_RX_SUCCESS)
+		goto drop;
+
+	/* Step 8: Flush pending store-carry-forward buffers for source node */
+	gn_ls_flush(gh->shb_h.sopv.addr);
+
+	return NET_RX_SUCCESS;
+drop:
+	kfree_skb(skb);
+	return NET_RX_DROP;
+}
+
+static int gn_process_tsb_packet(struct sk_buff *skb)
+{
+	struct btp_header *btp_h;
+	struct sockaddr_gn tosgn;
+	struct gn_header *gh;
+
+	gh = (struct gn_header *)skb_network_header(skb);
+	GN_SET_BTP(skb, btp_h, struct gn_tsb_header);
+
+	/* Step 3: Duplicate Address Detection (DAD) check */
+
+	if (gn_update_location_table(&gh->tsb_h.sopv, false, NULL,
+				     &gh->tsb_h.sn))
+		goto drop;
+
+	if (gh->gb_h.rhl > 0)
+		gn_forward_tsb_packet(skb);
+
+	// 7. pass payload of GN_PDU to the upper protocol unit
+	tosgn.sgn_family = PF_GN;
+	tosgn.sgn_addr = gh->tsb_h.sopv.addr;
+	tosgn.sgn_port = be16_to_cpu(btp_h->dst_port);
+
+	if (gn_pass_payload_sock(&tosgn, skb) != NET_RX_SUCCESS)
+		goto drop;
+
+	/* Step 8: Flush pending store-carry-forward buffers for source node */
+	gn_ls_flush(gh->tsb_h.sopv.addr);
+
+	return NET_RX_SUCCESS;
+drop:
+	kfree_skb(skb);
+	return NET_RX_DROP;
+}
+
+static int gn_process_beacon_packet(struct sk_buff *skb, const u8 *llc)
+{
+	struct gn_header *gh = (struct gn_header *)skb_network_header(skb);
+
+	if (gn_update_location_table(&gh->beacon_h.sopv, true, llc, NULL)) {
+		pr_debug("LocT update failure\n");
+		kfree_skb(skb);
+		return NET_RX_DROP;
+	}
+
+	gn_ls_flush(gh->beacon_h.sopv.addr);
+	kfree_skb(skb);
+	return NET_RX_SUCCESS;
+}
+
+static int gn_process_ls_packet(struct sk_buff *skb)
+{
+	gn_address_t dest_addr;
+	struct gn_iface *gnif;
+	struct gn_header *gh;
+
+	gh = (struct gn_header *)skb_network_header(skb);
+
+	if (gh->gc_h.hst == CH_HST_LS_REQUEST) {
+		struct gn_ls_request_header *gls_req_h = &gh->ls_request_h;
+
+		// 2. execute dad
+		// 3. update loc_te
+		dest_addr = gls_req_h->addr;
+		if (gn_update_location_table(&gls_req_h->sopv, false, NULL,
+					     &gls_req_h->sn))
+			goto drop;
+		// 4. find out if the packet has to be answered or forwarded
+		gnif = gn_find_interface(dest_addr);
+		if (gnif) {
+			// has to be answered
+			/* Note: gn_spv is a prefix (addr, tst, lat, lon) of gn_lpv */
+			gn_location_service_reply(
+				(struct gn_spv *)&gls_req_h->sopv, gnif,
+				dest_addr, 0);
+		} else {
+			// has to be forwarded like a tsb
+			// 5. try to flush own forward buffer
+			gn_ls_flush(gls_req_h->sopv.addr);
+			// 6. forward like a tsb
+			gn_forward_tsb_packet(skb);
+		}
+	} else if (gh->gc_h.hst == CH_HST_LS_REPLY) {
+		struct gn_ls_reply_header *gls_rep_h = &gh->ls_reply_h;
+		// 2. execute dad
+		// 3. update loc_te
+		if (gn_update_location_table(&gls_rep_h->sopv, false, NULL,
+					     &gls_rep_h->sn))
+			goto drop;
+		dest_addr = gls_rep_h->depv.addr;
+
+		// 4. flush forward buffer
+		gn_ls_flush(gls_rep_h->sopv.addr);
+		// 5. find out if the packet has to be forwarded
+		if (!gn_find_interface(dest_addr))
+			gn_forward_guc_packet(skb, dest_addr);
+	} else {
+		goto drop;
+	}
+	kfree_skb(skb);
+	return NET_RX_SUCCESS;
+drop:
+	//pr_info("Packet was dropped.");
+	kfree_skb(skb);
+	return NET_RX_DROP;
+}
+
+/**	gn_rcv() - Receive a packet (in skb) from device dev
+ *	@skb - packet received
+ *	@dev - network device where the packet comes from
+ *	@pt - packet type
+ *
+ *	Receive a packet (in skb) from device dev. This has come from the SNAP
+ *	decoder, and on entry skb->network_header is the GN Basic Header.
+ *	The physical headers have been extracted.
+ */
+static int gn_rcv(struct sk_buff *skb, struct net_device *dev,
+		  struct packet_type *pt, struct net_device *orig_dev)
+{
+	struct gn_header *gh;
+	struct ethhdr *eth;
+
+	if (!net_eq(dev_net(dev), &init_net))
+		goto drop;
+
+	if (dev->type != ARPHRD_ETHER)
+		goto drop;
+
+	if (!gn_find_interface_by_dev(dev))
+		goto drop;
+
+	skb = skb_share_check(skb, GFP_ATOMIC);
+
+	if (!skb)
+		goto drop;
+
+	eth = (struct ethhdr *)skb_mac_header(skb);
+	// TODO: validate
+	if (skb->pkt_type == PACKET_LOOPBACK ||
+	    ether_addr_equal(eth->h_source, dev->dev_addr))
+		goto drop;
+
+	skb_reset_network_header(skb);
+
+	if (!pskb_may_pull(skb, GN_BASE_HEADER_SIZE))
+		goto drop;
+
+	// Basic Header processing
+	gh = (struct gn_header *)skb_network_header(skb);
+
+	if (gh->gb_h.version != GN_VERSION ||
+	    gh->gb_h.nh != BH_NH_COMMON_HEADER) {
+		pr_debug("corrupt packet");
+		goto drop;
+	}
+
+	/*
+	 * Retrieve information from socketbuffer an insert it into according headers;
+	 * this is necessary to determine if the packet is ours and find the matching
+	 * socket, or if the packet has to be forwarded
+	 */
+	// Common Header processing
+
+	//skb_trim(skb, min_t(unsigned int, skb->len, gc_h->pl + sizeof(*gb_h)
+	// + sizeof(*gc_h)));
+
+	if (gh->gc_h.mhl < gh->gb_h.rhl)
+		goto drop;
+
+	switch (gh->gc_h.ht) {
+	case CH_HT_GUC:
+		if (!pskb_may_pull(skb, GN_BASE_HEADER_SIZE + GN_GUC_HLEN +
+						BTP_HLEN))
+			goto drop;
+		return gn_process_guc_packet(skb);
+	case CH_HT_GAC:
+	case CH_HT_GBC:
+		if (!pskb_may_pull(skb, GN_BASE_HEADER_SIZE +
+						GN_GXC_HLEN + BTP_HLEN)
+			goto drop;
+		return gn_process_gxc_packet(skb);
+	case CH_HT_TSB:
+		if (gh->gc_h.hst == CH_HST_TSB_SINGLE_HOP) {
+			if (!pskb_may_pull(skb, GN_BASE_HEADER_SIZE +
+							GN_SHB_HLEN + BTP_HLEN))
+				goto drop;
+			return gn_process_shb_packet(skb, eth->h_source);
+		}
+		if (gh->gc_h.hst == CH_HST_TSB_MULTI_HOP) {
+			if (!pskb_may_pull(skb, GN_BASE_HEADER_SIZE +
+							GN_TSB_HLEN + BTP_HLEN))
+				goto drop;
+			return gn_process_tsb_packet(skb);
+		}
+		goto drop;
+	case CH_HT_BEACON:
+		if (!pskb_may_pull(skb, GN_BASE_HEADER_SIZE + GN_BEACON_HLEN))
+			goto drop;
+		return gn_process_beacon_packet(skb, eth->h_source);
+	case CH_HT_LS:
+		if (gh->gc_h.hst == CH_HST_LS_REQUEST) {
+			if (!pskb_may_pull(skb, GN_BASE_HEADER_SIZE +
+							GN_LS_REQUEST_HLEN))
+				goto drop;
+		} else if (gh->gc_h.hst == CH_HST_LS_REPLY) {
+			if (!pskb_may_pull(skb, GN_BASE_HEADER_SIZE +
+							GN_LS_REPLY_HLEN))
+				goto drop;
+		} else {
+			goto drop;
+		}
+		return gn_process_ls_packet(skb);
+	default:
+		goto drop;
+	}
+drop:
+	kfree_skb(skb);
+	return NET_RX_DROP;
+}
+
+static int gn_fill_guc_header(struct gn_guc_header *guc_h,
+			      struct gn_iface *gnif, gn_address_t dest_addr,
+			      struct gn_spv *depv, struct gn_sock *gn)
+{
+	gn_address_t saddr = gn->src_addr ? gn->src_addr : gnif->address;
+
+	memset(guc_h, 0, sizeof(*guc_h));
+	guc_h->sn = cpu_to_be16(gn_if_next_sn(gnif));
+	gn_fill_sopv(gnif, &guc_h->sopv, saddr);
+	guc_h->depv.addr = dest_addr;
+	if (depv && depv->tst) {
+		guc_h->depv.tst = depv->tst;
+		guc_h->depv.lat = depv->lat;
+		guc_h->depv.lon = depv->lon;
+	} else {
+		guc_h->depv.tst = htonl(0);
+		guc_h->depv.lat = htonl(0);
+		guc_h->depv.lon = htonl(0);
+	}
+	return 0;
+}
+
+static int gn_fill_gxc_header(struct gn_gxc_header *gxc_h,
+			      struct gn_iface *gnif, struct gn_sock *gn)
+{
+	gn_address_t saddr = gn->src_addr ? gn->src_addr : gnif->address;
+
+	WARN_ON_ONCE(gn->scope.scope_type != GN_SCOPE_GEOGRAPHICAL &&
+		     gn->scope.scope_type != GN_SCOPE_GEOGRAPHICAL_ANYCAST);
+
+	memset(gxc_h, 0, sizeof(*gxc_h));
+	gxc_h->sn = cpu_to_be16(gn_if_next_sn(gnif));
+	gn_fill_sopv(gnif, &gxc_h->sopv, saddr);
+
+	gxc_h->gap_lat = cpu_to_be32(gn->scope.geo_scope.coord.lat);
+	gxc_h->gap_lon = cpu_to_be32(gn->scope.geo_scope.coord.lon);
+	gxc_h->angle = cpu_to_be16(gn->scope.geo_scope.angle);
+	switch (gn->scope.geo_scope.shape) {
+	case GN_SHAPE_CIRCLE:
+		gxc_h->da = cpu_to_be16(gn->scope.geo_scope.a);
+		break;
+	case GN_SHAPE_ELLIPSE:
+	case GN_SHAPE_RECTANGLE:
+		gxc_h->da = cpu_to_be16(gn->scope.geo_scope.a);
+		gxc_h->db = cpu_to_be16(gn->scope.geo_scope.b);
+		break;
+	default:
+	case GN_SHAPE_UNSPECIFIED:
+		pr_debug("unknown shape type");
+		break;
+	}
+	return 0;
+}
+
+static int gn_fill_shb_header(struct gn_shb_header *shb_h,
+			      struct gn_iface *gnif, struct gn_sock *gn)
+{
+	gn_address_t saddr = gn->src_addr ? gn->src_addr : gnif->address;
+
+	memset(shb_h, 0, sizeof(*shb_h));
+	gn_fill_sopv(gnif, &shb_h->sopv, saddr);
+
+	// prefill media dependent data field with empty
+	shb_h->mdd = htonl(0);
+	return 0;
+}
+
+static int gn_fill_tsb_header(struct gn_tsb_header *tsb_h,
+			      struct gn_iface *gnif, struct gn_sock *gn)
+{
+	gn_address_t saddr = gn->src_addr ? gn->src_addr : gnif->address;
+
+	memset(tsb_h, 0, sizeof(*tsb_h));
+	tsb_h->sn = cpu_to_be16(gn_if_next_sn(gnif));
+	gn_fill_sopv(gnif, &tsb_h->sopv, saddr);
+	return 0;
+}
+
+/**
+ * gn_sendmsg - always called if sendmsg() is called on a socket with an
+ * address family matching AF_GN
+ * @msghdr - contains information, including the payload of the packet
+ * @len - the size of the payload
+ */
+static int gn_sendmsg(struct socket *sock, struct msghdr *msg, size_t len)
+{
+	DECLARE_SOCKADDR(struct sockaddr_gn *, usgn, msg->msg_name);
+	int packet_type, packet_subtype, btp_type;
+	struct gn_common_header *gc_h;
+	struct gn_basic_header *gb_h;
+	struct sockaddr_gn local_sgn;
+	struct gn_spv depv = { 0 };
+	u8 rhl = DEFAULT_HOP_LIMIT;
+	int flags = msg->msg_flags;
+	struct btp_header *btp_h;
+	struct net_device *dev;
+	struct gn_iface *gnif;
+	struct sk_buff *skb;
+	struct gn_sock *gn;
+	int err = -EINVAL;
+	struct sock *sk;
+	u32 eh_size;
+	void *gp_h;
+	u32 size;
+
+	sk = sock->sk;
+	gn = gn_sk(sk);
+
+	if (flags & ~(MSG_DONTWAIT | MSG_CMSG_COMPAT)) {
+		pr_debug("unsupported sendmsg flags");
+		return -EINVAL;
+	}
+
+	if (len > GN_MAXSZ) {
+		pr_warn("message too long (got %zu, maximum %d)", len,
+			GN_MAXSZ);
+		return -EMSGSIZE;
+	}
+	lock_sock(sk);
+	if (usgn) {
+		err = -EBUSY;
+		if (sock_flag(sk, SOCK_ZAPPED)) {
+			pr_debug("socket zapped and autobind not implemented");
+			if (gn_autobind(sk) < 0)
+				goto out;
+		}
+		err = -EINVAL;
+		if (msg->msg_namelen < sizeof(*usgn) ||
+		    usgn->sgn_family != AF_GN) {
+			pr_debug("incorrect address family\n");
+			goto out;
+		}
+		//appletalk makes another check here
+	} else {
+		err = -ENOTCONN;
+		if (sk->sk_state != TCP_ESTABLISHED)
+			goto out;
+		usgn = &local_sgn;
+		usgn->sgn_family = AF_GN;
+		usgn->sgn_port = gn->dst_port;
+		usgn->sgn_addr = gn->dst_addr;
+	}
+	/*
+	 * Before the packet can be send, we have determine if the gn_address of
+	 * the source is bound to an interface. The interface provides the device
+	 * for sending the packet.
+	 */
+	gnif = gn_find_interface(gn->src_addr);
+
+	if (!gnif) {
+		pr_debug("Could not find an interface\n");
+		err = -EFAULT;
+		goto out;
+	}
+	dev = gnif->dev;
+
+	packet_subtype = CH_HST_UNSPECIFIED;
+	if (usgn->sgn_addr != 0 && usgn->sgn_addr != GNADDR_BROADCAST &&
+	    gn->scope.scope_type == GN_SCOPE_UNSPECIFIED) {
+		packet_type = CH_HT_GUC;
+	} else {
+		switch (gn->scope.scope_type) {
+		case GN_SCOPE_GEOGRAPHICAL:
+			packet_type = CH_HT_GBC;
+			packet_subtype =
+				gn_encode_shape(gn->scope.geo_scope.shape);
+			break;
+		case GN_SCOPE_GEOGRAPHICAL_ANYCAST:
+			packet_type = CH_HT_GAC;
+			packet_subtype =
+				gn_encode_shape(gn->scope.geo_scope.shape);
+			break;
+		case GN_SCOPE_TOPOLOGICAL:
+			packet_type = CH_HT_TSB;
+			if (gn->scope.topo_hops <= 1)
+				packet_subtype = CH_HST_TSB_SINGLE_HOP;
+			else
+				packet_subtype = CH_HST_TSB_MULTI_HOP;
+			break;
+		case GN_SCOPE_UNSPECIFIED:
+			packet_type = CH_HT_TSB;
+			packet_subtype = CH_HST_TSB_SINGLE_HOP;
+			break;
+		default:
+			err = -EINVAL;
+			goto out;
+		}
+	}
+
+	switch (gn->protocol) {
+	case GN_PROTO_BTP_A:
+		btp_type = CH_NH_BTPA;
+		break;
+	case GN_PROTO_BTP_B:
+		btp_type = CH_NH_BTPB;
+		break;
+	default:
+		err = -EINVAL;
+		goto out;
+	}
+
+	// Determine extended (header type specific) header size
+	switch (packet_type) {
+	case CH_HT_GUC:
+		eh_size = sizeof(struct gn_guc_header);
+		break;
+	case CH_HT_GAC:
+	case CH_HT_GBC:
+		eh_size = sizeof(struct gn_gxc_header);
+		break;
+	case CH_HT_TSB:
+		if (packet_subtype == CH_HST_TSB_SINGLE_HOP) {
+			eh_size = sizeof(struct gn_shb_header);
+		} else if (packet_subtype == CH_HST_TSB_MULTI_HOP) {
+			eh_size = sizeof(struct gn_tsb_header);
+		} else {
+			pr_debug("internal error");
+			err = -EINVAL;
+			goto out;
+		}
+		break;
+	default:
+		pr_debug("internal error");
+		err = -EINVAL;
+		goto out;
+	}
+
+	/*
+	 * Find out, how much memory has to be allocated for the packet and
+	 * allocate it.
+	 */
+	size = gn_dl->header_length;
+	size += dev->hard_header_len;
+	size += sizeof(struct gn_basic_header);
+	size += sizeof(struct gn_common_header);
+	size += eh_size;
+	size += sizeof(struct btp_header);
+	size += len;
+
+	release_sock(sk);
+	skb = sock_alloc_send_skb(sk, size, (flags & MSG_DONTWAIT), &err);
+	lock_sock(sk);
+	if (!skb) {
+		err = -ENOMEM;
+		goto out;
+	}
+	/*
+	 * Reserve memory in the socketbuffer for datalink, hardware, our headers
+	 */
+	skb_reserve(skb, gn_dl->header_length);
+	skb_reserve(skb, dev->hard_header_len);
+	skb_reserve(skb, sizeof(struct gn_basic_header));
+	skb_reserve(skb, sizeof(struct gn_common_header));
+	skb_reserve(skb, eh_size);
+	skb_reserve(skb, sizeof(struct btp_header));
+
+	skb->dev = dev;
+	/*
+	 * Copy userdata from socket into the payload of the packet
+	 */
+	err = memcpy_from_msg(skb_put(skb, len), msg, len);
+	if (err) {
+		pr_debug("could not extract from msg\n");
+		kfree_skb(skb);
+		err = -EFAULT;
+		goto out;
+	}
+
+	/*
+	 * Get positions of headers and fill them
+	 */
+	btp_h = skb_push(skb, sizeof(struct btp_header));
+	gp_h = skb_push(skb, eh_size);
+	gc_h = skb_push(skb, sizeof(struct gn_common_header));
+	gb_h = skb_push(skb, sizeof(struct gn_basic_header));
+
+	switch (btp_type) {
+	case CH_NH_BTPA:
+		btp_h->src_port = htons(gn->src_port);
+		btp_h->dst_port = htons(usgn->sgn_port);
+		break;
+	case CH_NH_BTPB:
+		btp_h->dst_port = htons(usgn->sgn_port);
+		btp_h->src_port = 0;
+		break;
+	default:
+		pr_debug("internal error");
+		kfree_skb(skb);
+		err = -EINVAL;
+		goto out;
+	}
+
+	switch (packet_type) {
+	case CH_HT_GUC:
+		gn_fill_depv(&depv, usgn->sgn_addr);
+		gn_fill_guc_header((struct gn_guc_header *)gp_h, gnif,
+				   usgn->sgn_addr, &depv, gn);
+		break;
+	case CH_HT_GAC:
+	case CH_HT_GBC:
+		gn_fill_gxc_header((struct gn_gxc_header *)gp_h, gnif, gn);
+		break;
+	case CH_HT_TSB:
+		if (packet_subtype == CH_HST_TSB_SINGLE_HOP) {
+			rhl = 1;
+			gn_fill_shb_header((struct gn_shb_header *)gp_h, gnif,
+					   gn);
+		} else if (packet_subtype == CH_HST_TSB_MULTI_HOP) {
+			//at this point it is safe to assume that topological scope is used
+			rhl = gn->scope.topo_hops ?: DEFAULT_HOP_LIMIT;
+			gn_fill_tsb_header((struct gn_tsb_header *)gp_h, gnif,
+					   gn);
+		} else {
+			pr_debug("internal error");
+			kfree_skb(skb);
+			err = -EINVAL;
+			goto out;
+		}
+		break;
+	}
+
+	/* Note: BTP header type (A/B) is determined by socket protocol */
+	gn_fill_bh_ch(gnif, (struct gn_header *)gb_h, packet_type,
+		      packet_subtype, rhl, btp_type,
+		      htons(len + sizeof(struct btp_header)));
+
+	/* Note: DEPV is populated during location service queue processing */
+	if (packet_type == CH_HT_GUC) {
+		u8 ll_address[ETH_ALEN];
+		int queue_rc = gn_ls_queue(usgn->sgn_addr, skb);
+
+		switch (queue_rc) {
+		case GN_QUEUE_LS_STALE:
+			// We need to perform a LS request
+			gn_location_service_req(gnif, gn->src_addr,
+						usgn->sgn_addr);
+			break;
+		case GN_QUEUE_LS_PENDING:
+			// LS request is pending, we're done
+			break;
+		case GN_QUEUE_DIRECT:
+			/* Destination in LocTE; resolve MAC or greedy next-hop */
+			if (gn_query_ll_nexthop(gnif, usgn->sgn_addr,
+						ll_address))
+				gn_dl->request(gn_dl, skb, dev->broadcast);
+			else
+				gn_dl->request(gn_dl, skb, ll_address);
+			break;
+		case GN_QUEUE_ERROR:
+		default:
+			kfree_skb(skb);
+			err = -ENOMEM;
+			goto out;
+		}
+	} else {
+		// Not a unicast packet
+		gn_dl->request(gn_dl, skb, dev->broadcast);
+	}
+
+	/* Destination position vector routing handled by location service queue */
+
+	err = len;
+out:
+	release_sock(sk);
+	return err;
+}
+
+/**
+ * gn_recvmsg - called, when recv() is called on a socket with AF = AF_GN.
+ * Copies the data of a received packet into msg.
+ * @sock - socket, on which recv() was called
+ * @msg - buffer used to retrieve packetdata
+ * @size - size of msg
+ * @flags - flags for receiving
+ *
+ */
+
+static int gn_recvmsg(struct socket *sock, struct msghdr *msg, size_t size,
+		      int flags)
+{
+	struct sock *sk = sock->sk;
+	struct gn_header *gh;
+	struct sk_buff *skb;
+	u16 copied = 0;
+	int err = 0;
+	u32 offset;
+
+	/* It is necessary to find the actual length of the payload, which,
+	 * in case of an unsecured package, resides in the commonheader and
+	 * still has to be calculated without the length of the btp-header
+	 * (4byte).
+	 * Furthermore, the datapointer probably has to be set to the
+	 * beginning of the actual payload
+	 */
+	skb = skb_recv_datagram(sk, flags & MSG_DONTWAIT, &err);
+	lock_sock(sk);
+	if (!skb)
+		goto out;
+
+	/* Note: Socket validation checks performed during bind/connect */
+	gh = (struct gn_header *)skb_network_header(skb);
+
+	copied = be16_to_cpu(gh->gc_h.pl);
+	offset = skb->transport_header - skb->network_header +
+		 sizeof(struct btp_header);
+	copied -= sizeof(struct btp_header);
+
+	/* Handle truncated datagram reception when user buffer is smaller than
+	 * payload */
+	if (copied > size) {
+		copied = size;
+		msg->msg_flags |= MSG_TRUNC;
+	}
+
+	err = skb_copy_datagram_msg(skb, offset, msg, copied);
+
+	skb_free_datagram(sk, skb);
+
+out:
+	release_sock(sk);
+	return err ?: copied;
+}
+
+/*
+ * Geonetworking timer callbacks
+ */
+static int gn_send_beacon(struct gn_iface *gnif)
+{
+	/* Note: Media dependent procedures (e.g. ITS-G5 DCC / DCC Access)
+	 * are evaluated here */
+	struct gn_beacon_header *gbe_h;
+	struct gn_common_header *gc_h;
+	struct gn_basic_header *gb_h;
+	struct sk_buff *skb;
+	unsigned int size;
+
+	size = gn_dl->header_length;
+	size += gnif->dev->hard_header_len;
+	size += sizeof(struct gn_basic_header);
+	size += sizeof(struct gn_common_header);
+	size += sizeof(struct gn_beacon_header);
+
+	skb = netdev_alloc_skb(gnif->dev, size);
+	if (!skb)
+		goto out;
+
+	skb_reserve(skb, size);
+	skb->dev = gnif->dev;
+
+	gbe_h = skb_push(skb, sizeof(struct gn_beacon_header));
+	gc_h = skb_push(skb, sizeof(struct gn_common_header));
+	gb_h = skb_push(skb, sizeof(struct gn_basic_header));
+
+	gn_fill_bh_ch_nopayload(gnif, (struct gn_header *)gb_h, CH_HT_BEACON,
+				CH_HST_BEACON, 1);
+
+	gn_fill_sopv(gnif, &gbe_h->sopv, gnif->address);
+
+	gn_dl->request(gn_dl, skb, gnif->dev->broadcast);
+
+out:
+	return 0;
+}
+
+static void gn_send_beacons(struct timer_list *tl)
+{
+	struct gn_iface *gnif;
+	bool empty = true;
+
+	rcu_read_lock();
+	hlist_for_each_entry_rcu(gnif, &gn_interfaces, hnode) {
+		gn_send_beacon(gnif);
+		empty = false;
+	}
+	rcu_read_unlock();
+
+	if (!empty)
+		mod_timer(tl, jiffies + msecs_to_jiffies(
+						GN_BEACON_RETRANSMIT_TIME));
+}
+
+static void gn_activate_beacon(void)
+{
+	mod_timer(&gn_beacon_timer,
+		  jiffies + msecs_to_jiffies(GN_BEACON_RETRANSMIT_TIME));
+}
+
+static int validate_geo_scope(struct gn_geo_scope *scope)
+{
+	if (scope->angle > 360)
+		return -EINVAL;
+
+	if (scope->a == 0)
+		return -EINVAL;
+
+	switch (scope->shape) {
+	case GN_SHAPE_CIRCLE:
+		return scope->b != 0 ? -EINVAL : 0;
+	case GN_SHAPE_RECTANGLE:
+	case GN_SHAPE_ELLIPSE:
+		return 0;
+	default:
+	case GN_SHAPE_UNSPECIFIED:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int validate_scope(struct gn_scope *scope)
+{
+	if (scope->scope_type < GN_SCOPE_TOPOLOGICAL ||
+	    scope->scope_type > GN_SCOPE_MAX)
+		return -EINVAL;
+	switch (scope->scope_type) {
+	case GN_SCOPE_TOPOLOGICAL:
+		return 0;
+	case GN_SCOPE_GEOGRAPHICAL:
+	case GN_SCOPE_GEOGRAPHICAL_ANYCAST:
+		return validate_geo_scope(&scope->geo_scope);
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int gn_setsockopt(struct socket *sock, int level, int optname,
+			 sockptr_t optval, unsigned int optlen)
+{
+	struct sock *sk = sock->sk;
+	int rc = -ENOPROTOOPT;
+	struct gn_scope opt;
+	struct gn_sock *gn;
+
+	gn = gn_sk(sk);
+
+	if (level != SOL_GN || optname != GN_SCOPE)
+		goto out;
+
+	rc = -EINVAL;
+	if (optlen < sizeof(struct gn_scope))
+		goto out;
+
+	rc = -EFAULT;
+	if (copy_from_sockptr(&opt, optval, sizeof(struct gn_scope)))
+		goto out;
+
+	rc = validate_scope(&opt);
+	if (rc < 0)
+		goto out;
+
+	lock_sock(sk);
+	memcpy(&gn->scope, &opt, sizeof(struct gn_scope));
+	release_sock(sk);
+
+	rc = 0;
+out:
+	return rc;
+}
+
+static int gn_getsockopt(struct socket *sock, int level, int optname,
+			 char __user *optval, int __user *optlen)
+{
+	int len, rc = -ENOPROTOOPT;
+	struct sock *sk = sock->sk;
+	struct gn_sock *gn;
+
+	gn = gn_sk(sk);
+
+	if (level != SOL_GN || optname != GN_SCOPE)
+		goto out;
+
+	rc = -EFAULT;
+	if (get_user(len, optlen))
+		goto out;
+
+	rc = -EINVAL;
+	if (len < 0)
+		goto out;
+
+	len = min_t(unsigned int, len, sizeof(struct gn_scope));
+
+	rc = -EFAULT;
+	if (put_user(len, optlen))
+		goto out;
+
+	lock_sock(sk);
+	rc = copy_to_user(optval, &gn->scope, len) ? -EFAULT : 0;
+	release_sock(sk);
+out:
+	return rc;
+}
+
+/*
+ * validate a gn_position coming from userspace
+ */
+int gn_validate_pos(struct gn_position *pos)
+{
+	if (pos->tst.tv_sec < 0 || pos->tst.tv_nsec < 0 ||
+	    pos->tst.tv_nsec >= NSEC_PER_SEC)
+		return -EINVAL;
+	return 0;
+}
+
+static int gn_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+	struct sock *sk = sock->sk;
+	int rc = -ENOIOCTLCMD;
+
+	switch (cmd) {
+	/* Protocol layer */
+	case TIOCOUTQ: {
+		long amount = sk->sk_sndbuf - sk_wmem_alloc_get(sk);
+
+		if (amount < 0)
+			amount = 0;
+		rc = put_user(amount, (int __user *)argp);
+		break;
+	}
+	case TIOCINQ: {
+		/*
+		 * These two are safe on a single CPU system as only
+		 * user tasks fiddle here
+		 */
+		struct sk_buff *skb = skb_peek(&sk->sk_receive_queue);
+		long amount = 0;
+
+		if (skb)
+			amount = skb->len - sizeof(struct gn_header);
+		rc = put_user(amount, (int __user *)argp);
+		break;
+	}
+	case SIOCGSTAMP_OLD:
+	case SIOCGSTAMP_NEW:
+	case SIOCGSTAMPNS_OLD:
+	case SIOCGSTAMPNS_NEW:
+		break;
+	}
+
+	return rc;
+}
+
+#ifdef CONFIG_COMPAT
+static int gn_compat_ioctl(struct socket *sock, unsigned int cmd,
+			   unsigned long arg)
+{
+	/* All GeoNetworking ioctl commands (TIOCOUTQ, TIOCINQ, SIOCGIFADDR, etc.)
+	 * and struct gn_position (using struct __kernel_timespec) are 64-bit
+	 * clean and compat-safe.
+	 */
+	return gn_ioctl(sock, cmd, arg);
+}
+#endif
+
+static const struct net_proto_family gn_family_ops = {
+	.family = PF_GN,
+	.create = gn_create,
+	.owner = THIS_MODULE,
+};
+
+static const struct proto_ops gn_dgram_ops = {
+	.family = PF_GN,
+	.owner = THIS_MODULE,
+	.release = gn_release,
+	.bind = gn_bind,
+	.connect = gn_connect,
+	.socketpair = sock_no_socketpair,
+	.accept = sock_no_accept,
+	.getname = sock_no_getname,
+	.poll = datagram_poll,
+	.ioctl = gn_ioctl,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl = gn_compat_ioctl,
+#endif
+	.listen = sock_no_listen,
+	.shutdown = sock_no_shutdown,
+	.setsockopt = gn_setsockopt,
+	.getsockopt = gn_getsockopt,
+	.sendmsg = gn_sendmsg,
+	.recvmsg = gn_recvmsg,
+	.mmap = sock_no_mmap,
+};
+
+static struct notifier_block gn_notifier = {
+	.notifier_call = gn_device_event,
+};
+
+static struct packet_type gn_packet_type __read_mostly = {
+	.type = cpu_to_be16(ETH_P_GN),
+	.func = gn_rcv,
+};
+
+/*
+ * SNAP-ID for Geonetworking 0x8947
+ * Note: SNAP header format uses big-endian 0x8947 per ETSI EN 302 636-4-1
+ */
+static unsigned char gn_snap_id[] = { 0x00, 0x00, 0x00, 0x89, 0x47 };
+
+/* Called by proto.c on kernel start up */
+static int __init gn_init(void)
+{
+	int rc;
+
+	timer_setup(&gn_beacon_timer, gn_send_beacons, 0);
+
+	rc = proto_register(&gn_proto, 0);
+	if (rc)
+		return rc;
+
+	rc = sock_register(&gn_family_ops);
+	if (rc)
+		goto out_proto;
+
+	gn_dl = register_snap_client(gn_snap_id, gn_rcv);
+	if (!gn_dl) {
+		pr_warn("Unable to register GeoNetworking with SNAP.\n");
+		rc = -ENOMEM;
+		goto out_snap;
+	}
+
+	dev_add_pack(&gn_packet_type);
+
+	rc = register_netdevice_notifier(&gn_notifier);
+	if (rc)
+		goto out_dev;
+
+#ifdef CONFIG_SYSCTL
+	rc = gn_register_sysctl();
+	if (rc)
+		goto out_nd;
+#endif
+
+	rc = gn_netlink_init();
+	if (rc)
+		goto out_sysctl;
+
+	return 0;
+
+out_sysctl:
+#ifdef CONFIG_SYSCTL
+	gn_unregister_sysctl();
+#endif
+out_nd:
+	unregister_netdevice_notifier(&gn_notifier);
+out_dev:
+	dev_remove_pack(&gn_packet_type);
+	unregister_snap_client(gn_dl);
+out_snap:
+	sock_unregister(PF_GN);
+out_proto:
+	proto_unregister(&gn_proto);
+	return rc;
+}
+module_init(gn_init);
+
+static void __exit gn_exit(void)
+{
+	gn_netlink_exit();
+
+#ifdef CONFIG_SYSCTL
+	gn_unregister_sysctl();
+#endif /* CONFIG_SYSCTL */
+
+	timer_shutdown_sync(&gn_beacon_timer);
+
+	unregister_netdevice_notifier(&gn_notifier);
+	dev_remove_pack(&gn_packet_type);
+	unregister_snap_client(gn_dl);
+	sock_unregister(PF_GN);
+	proto_unregister(&gn_proto);
+
+	gn_interfaces_clear();
+	gn_routing_exit();
+	rcu_barrier();
+}
+module_exit(gn_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Mr Noname <email.here@domain");
+MODULE_DESCRIPTION("GeoNetworking protocol");
+MODULE_ALIAS_NETPROTO(PF_GN);
diff --git a/net/gn/gn_routing.c b/net/gn/gn_routing.c
new file mode 100644
index 000000000000..7d8df7165ecb
--- /dev/null
+++ b/net/gn/gn_routing.c
@@ -0,0 +1,646 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__
+#include <linux/types.h>
+#include <linux/bitfield.h>
+#include <linux/hashtable.h>
+#include <linux/spinlock.h>
+#include <linux/skbuff.h>
+#include <net/datalink.h>
+
+#include <linux/gn_routing.h>
+#include <linux/gn.h>
+
+extern struct datalink_proto *gn_dl;
+
+static DEFINE_HASHTABLE(gn_loc_t, 3);
+static DEFINE_SPINLOCK(gn_loc_t_lock);
+
+// ###### values in meters
+#define EARTH_RADIUS 6378388ULL
+#define PI 31415926ULL // * 10^7
+#define METER_PER_LON 111317ULL
+#define RAD_PER_DEGREE 174533ULL // * 10^7 - same as long and lat from PV
+
+#define GN_LT_JIFFIES msecs_to_jiffies(GN_LOC_TE_LIFETIME)
+#define GN_TST_VALID(tst) \
+	time_before(jiffies, (unsigned long)(tst) + GN_LT_JIFFIES)
+
+/* GeoNetworking routing */
+
+static u16 *gn_dpd_find(struct gn_dpd_buf *buf, u16 sn)
+{
+	int i = 0;
+
+	for (; i < GN_DPL_SIZE; i++) {
+		if (buf->sn[i] == sn)
+			return &buf->sn[i];
+	}
+	return NULL;
+}
+
+static void gn_dpd_insert(struct gn_dpd_buf *buf, u16 sn)
+{
+	buf->sn[buf->head] = sn;
+	buf->head = (buf->head + 1) % GN_DPL_SIZE;
+}
+
+static u32 pdr(u32 old_pdr, u32 delta)
+{
+	if (!delta)
+		delta = 1;
+	return (GN_MAX_PDR_EMA_BETA * old_pdr) / 100 +
+	       (((100 - GN_MAX_PDR_EMA_BETA) * (100 / delta)) / 100);
+}
+
+// table is * 1000 | p1 * 100 entry
+static const int cos_table[] = {
+	100000, 99995,	99980,	99955,	99920,	99875,	99820,	99755,	99680,
+	99595,	99500,	99396,	99281,	99156,	99022,	98877,	98723,	98558,
+	98384,	98200,	98007,	97803,	97590,	97367,	97134,	96891,	96639,
+	96377,	96106,	95824,	95534,	95233,	94924,	94604,	94275,	93937,
+	93590,	93233,	92866,	92491,	92106,	91712,	91309,	90897,	90475,
+	90045,	89605,	89157,	88699,	88233,	87758,	87274,	86782,	86281,
+	85771,	85252,	84726,	84190,	83646,	83094,	82534,	81965,	81388,
+	80803,	80210,	79608,	78999,	78382,	77757,	77125,	76484,	75836,
+	75181,	74517,	73847,	73169,	72484,	71791,	71091,	70385,	69671,
+	68950,	68222,	67488,	66746,	65998,	65244,	64483,	63715,	62941,
+	62161,	61375,	60582,	59783,	58979,	58168,	57352,	56530,	55702,
+	54869,	54030,	53186,	52337,	51482,	50622,	49757,	48887,	48012,
+	47133,	46249,	45360,	44466,	43568,	42666,	41759,	40849,	39934,
+	39015,	38092,	37166,	36236,	35302,	34365,	33424,	32480,	31532,
+	30582,	29628,	28672,	27712,	26750,	25785,	24818,	23848,	22875,
+	21901,	20924,	19945,	18964,	17981,	16997,	16010,	15023,	14033,
+	13042,	12050,	11057,	10063,	9067,	8071,	7074,	6076,	5077,
+	4079,	3079,	2079,	1080,	0,	-920,	-1920,	-2920,	-3919,
+	-4918,	-5917,	-6915,	-7912,	-8909,	-9904,	-10899, -11892, -12884,
+	-13875, -14865, -15853, -16840, -17825, -18808, -19789, -20768, -21745,
+	-22720, -23693, -24663, -25631, -26596, -27559, -28519, -29476, -30430,
+	-31381, -32329, -33274, -34215, -35153, -36087, -37018, -37945, -38868,
+	-39788, -40703, -41615, -42522, -43425, -44323, -45218, -46107, -46992,
+	-47873, -48748, -49619, -50485, -51345, -52201, -53051, -53896, -54736,
+	-55570, -56399, -57221, -58039, -58850, -59656, -60455, -61249, -62036,
+	-62817, -63592, -64361, -65123, -65879, -66628, -67370, -68106, -68834,
+	-69556, -70271, -70979, -71680, -72374, -73060, -73739, -74411, -75075,
+	-75732, -76382, -77023, -77657, -78283, -78901, -79512, -80114, -80709,
+	-81295, -81873, -82444, -83005, -83559, -84104, -84641, -85169, -85689,
+	-86200, -86703, -87197, -87682, -88158, -88626, -89085, -89534, -89975,
+	-90407, -90830, -91244, -91648, -92044, -92430, -92807, -93175, -93533,
+	-93883, -94222, -94553, -94873, -95185, -95486, -95779, -96061, -96334,
+	-96598, -96852, -97096, -97330, -97555, -97770, -97975, -98170, -98356,
+	-98531, -98697, -98853, -98999, -99135, -99262, -99378, -99484, -99581,
+	-99667, -99744, -99810, -99867, -99914, -99950, -99977, -99993, -100000
+};
+
+/* icos() - look up in cos_table for a rad value.
+ * @rad : the rad value.* 10^7
+ *
+ * Return : the cosine value * 100000
+ */
+static int icos(__s64 rad)
+{
+	size_t idx;
+
+	if (rad < 0)
+		rad = -rad;
+
+	if (rad >= 2 * PI)
+		rad %= (2 * PI);
+
+	if (rad > PI)
+		rad = 2 * PI - rad;
+
+	idx = rad / 100000;
+	if (idx >= ARRAY_SIZE(cos_table))
+		idx = ARRAY_SIZE(cos_table) - 1;
+
+	return cos_table[idx];
+}
+
+/* degree_to_rad() - convert a degree value to a rad value.
+ * @a : the degree value as 1/10 micro degree (10^7).
+ *
+ * Return : the rad value * 10^7.
+ */
+static __s64 degree_to_rad(__s64 a)
+{
+	return (((RAD_PER_DEGREE * a) / 10000000ULL)) % (PI * 2ULL);
+}
+
+/* diff() - calculate the difference beween a and b.
+ * @a : value a.
+ * @b : value b.
+ *
+ * Return : the difference
+ */
+static __s32 diff(__s32 a, __s32 b)
+{
+	__s32 r = a - b;
+
+	return r < 0 ? r * -1 : r;
+}
+
+/* get_distance() - calculate the distance of to position vectors
+ * center/self: positionvectors, whose distance is calculated
+ * @x : after execute includes the meter on X-axes.
+ * @y : after execute includes the meter on Y-axes.
+ *
+ * the calculation based on pythagoras.
+ */
+static struct gn_coord gn_coord_diff(struct gn_coord lhs, struct gn_coord rhs)
+{
+	struct gn_coord c;
+	s32 lat;
+
+	lat = degree_to_rad((lhs.lat + rhs.lat) / 2);
+
+	c.lat = (METER_PER_LON * icos(lat) * diff(rhs.lon, lhs.lon)) /
+		(10000000ULL * 100000ULL);
+	c.lon = (METER_PER_LON * diff(rhs.lat, lhs.lat)) / 10000000ULL;
+	return c;
+}
+
+static inline struct gn_coord pv_to_coord(struct gn_lpv *pv)
+{
+	struct gn_coord c = {
+		.lat = be32_to_cpu(pv->lat),
+		.lon = be32_to_cpu(pv->lon),
+	};
+	return c;
+}
+
+static inline u64 dist(struct gn_coord c)
+{
+	return c.lat * c.lat + c.lon * c.lon;
+}
+
+/* gn_F() - decides if self is inside or at the border of the geographical area.
+ * @center: The position vector of the Package sender.
+ * @self: The own position vector.
+ * @t : The Type of geographical area.
+ * @r : The radius of area. Only use for circle.
+ * @a : The width of area. Only use for rectangel and elipse.
+ * @b : The height of area. Only use for rectangel and elipse.
+ * @angel : ???
+ *
+ * Return:  if the result is > 0 self is inside area.
+			if the result is 0 self is on border of area.
+			otherwise self is outside of area.
+ */
+__s64 gn_F(struct gn_coord self, struct gn_geo_scope scope)
+{
+	struct gn_coord coord_diff = gn_coord_diff(self, scope.coord);
+	s32 a2, b2, x2, y2;
+	s64 result = -1;
+	/* Note: Scope angle rotation for non-circular geographical areas */
+	a2 = scope.a * scope.a;
+	b2 = scope.b * scope.b;
+	x2 = coord_diff.lat * coord_diff.lat;
+	y2 = coord_diff.lon * coord_diff.lon;
+
+	switch (scope.shape) {
+	case GN_SHAPE_CIRCLE:
+		result = a2 - (x2 + y2);
+		break;
+	case GN_SHAPE_RECTANGLE:
+		result = a2 - x2;
+		if (result > (b2 - y2))
+			result = b2 - y2;
+		break;
+	case GN_SHAPE_ELLIPSE:
+		if (scope.a == 0 || scope.b == 0) {
+			pr_debug("internal error: input out of range");
+			break;
+		}
+		result = 1000;
+		result -= (y2 * 1000) / a2;
+		result -= (x2 * 1000) / b2;
+		break;
+	}
+
+	return result;
+}
+
+static int greedy_forward(struct gn_iface *gnif, u8 *addr, struct gn_lpv *depv)
+{
+	struct gn_coord dest_coord = pv_to_coord(depv);
+	u64 min_dist, curr_dist, ego_dist;
+	u8 *found_addr = NULL;
+	struct loc_te *curr;
+	int bkt, rc;
+
+	ego_dist = dist(gn_coord_diff(dest_coord, gnif->pos.coord));
+	min_dist = ego_dist;
+	spin_lock_bh(&gn_loc_t_lock);
+	hash_for_each(gn_loc_t, bkt, curr, hnode) {
+		if (curr->is_neighbour) {
+			curr_dist = dist(gn_coord_diff(dest_coord,
+						       pv_to_coord(&curr->pv)));
+			if (curr_dist < min_dist) {
+				min_dist = curr_dist;
+				found_addr = curr->ll_address;
+			}
+		}
+	}
+	/* Note: Traffic class and store-carry-forward evaluation for next-hop selection */
+	if (found_addr) {
+		ether_addr_copy(addr, found_addr);
+		rc = GN_FORWARD_NEXT_HOP;
+	} else {
+		rc = GN_FORWARD_BROADCAST;
+	}
+	spin_unlock_bh(&gn_loc_t_lock);
+
+	return rc;
+}
+
+int gn_gxc_forward(struct gn_iface *gnif, s64 f, u8 *addr, struct gn_lpv *depv)
+{
+	if (f >= 0) {
+		// Area forwarding
+		switch (GN_AREA_FORWARDING) {
+		case GN_AREA_FORWARDING_SIMPLE:
+			return GN_FORWARD_BROADCAST;
+		default:
+			pr_warn("non-simple area forwarding not implemented");
+			return GN_FORWARD_BROADCAST;
+		}
+	} else {
+		// Non-area forwarding
+		switch (GN_NON_AREA_FORWARDING) {
+		case GN_NON_AREA_FORWARDING_GREEDY:
+			return greedy_forward(gnif, addr, depv);
+		default:
+			return GN_FORWARD_BROADCAST;
+		}
+	}
+}
+
+static void debug_loc_te(void)
+{
+	struct hlist_node *tmp;
+	struct loc_te *entry;
+	int bucket;
+
+	spin_lock_bh(&gn_loc_t_lock);
+	if (hash_empty(gn_loc_t)) {
+		spin_unlock_bh(&gn_loc_t_lock);
+		return;
+	}
+
+	pr_debug("Printing location table\n");
+	hash_for_each_safe(gn_loc_t, bucket, tmp, entry, hnode) {
+		pr_debug(
+			"LOC_TE(%p) tst=%x addr=%llx ll_addr=%llx is_neighbour=%x ls_pending=%x\n",
+			entry, entry->tst_addr, be64_to_cpu(entry->addr),
+			ether_addr_to_u64(entry->ll_address),
+			entry->is_neighbour, entry->ls_pending);
+	}
+	spin_unlock_bh(&gn_loc_t_lock);
+}
+
+static void gn_prune(void)
+{
+	struct hlist_node *tmp;
+	struct loc_te *entry;
+	int bucket;
+
+	spin_lock_bh(&gn_loc_t_lock);
+	hash_for_each_safe(gn_loc_t, bucket, tmp, entry, hnode) {
+		if (!GN_TST_VALID(entry->tst_addr)) {
+			pr_debug("pruning entry addr=%llx\n", entry->addr);
+			skb_queue_purge(&entry->lsb);
+			hash_del(&entry->hnode);
+			kfree(entry);
+		}
+	}
+	spin_unlock_bh(&gn_loc_t_lock);
+}
+
+/* update_location_table() - update location table
+ * @pv: the position vector which indicate an entry.
+ *
+ * Return: 0 on success, negative errno on error, -EALREADY if packet is duplicate.
+ *
+ * Update an entry, which indicated by @spv. If no entry found its will be add a new one.
+ * And all entries will be check with the update function.
+ */
+int gn_update_location_table(struct gn_lpv *pv, bool make_neighbour,
+			     const u8 *ll_address, const __be16 *sn)
+{
+	/* ETSI EN 302 636-4-1 Clause C.2: Update PV only when incoming PV timestamp is newer */
+	struct loc_te *entry;
+	bool found = false;
+
+	if (gn_find_interface(pv->addr))
+		return -EINVAL;
+
+	spin_lock_bh(&gn_loc_t_lock);
+	hash_for_each_possible(gn_loc_t, entry, hnode, pv->addr) {
+		if (entry->addr != pv->addr)
+			continue;
+
+		found = true;
+
+		pr_debug("updating entry addr=%llx\n", pv->addr);
+
+		entry->pdr = pdr(entry->pdr,
+				 jiffies_to_msecs(jiffies) -
+					 jiffies_to_msecs(entry->tst_addr));
+		entry->tst_addr = jiffies;
+		if (make_neighbour) {
+			if (!ll_address) {
+				spin_unlock_bh(&gn_loc_t_lock);
+				return -EINVAL;
+			}
+			ether_addr_copy(entry->ll_address, ll_address);
+			entry->is_neighbour = true;
+		}
+		memcpy(&entry->pv, pv, sizeof(*pv));
+		if (sn) {
+			// Perform DPD
+			if (gn_dpd_find(&entry->dpl, be16_to_cpu(*sn))) {
+				pr_debug("received duplicate packet\n");
+				spin_unlock_bh(&gn_loc_t_lock);
+				return -EALREADY;
+			}
+			gn_dpd_insert(&entry->dpl, be16_to_cpu(*sn));
+		}
+		break;
+	}
+
+	if (!found) {
+		entry = kzalloc_obj(*entry, GFP_ATOMIC);
+		if (!entry) {
+			spin_unlock_bh(&gn_loc_t_lock);
+			return -ENOMEM;
+		}
+		pr_debug("adding entry addr=%llx\n", pv->addr);
+		entry->addr = pv->addr;
+		entry->tst_addr = jiffies;
+		entry->is_neighbour = make_neighbour;
+		memcpy(&entry->pv, pv, sizeof(*pv));
+		skb_queue_head_init(&entry->lsb);
+		if (make_neighbour) {
+			if (!ll_address) {
+				kfree(entry);
+				spin_unlock_bh(&gn_loc_t_lock);
+				return -EINVAL;
+			}
+			ether_addr_copy(entry->ll_address, ll_address);
+		}
+		if (sn)
+			gn_dpd_insert(&entry->dpl, be16_to_cpu(*sn));
+
+		hash_add(gn_loc_t, &entry->hnode, entry->addr);
+	}
+	spin_unlock_bh(&gn_loc_t_lock);
+
+	gn_prune();
+
+	debug_loc_te();
+
+	return 0;
+}
+
+static void __ls_queue(struct sk_buff_head *q, struct sk_buff *skb)
+{
+	u32 qlen = skb_queue_len(q);
+	struct sk_buff *curr;
+
+	while (qlen-- > GN_LSB_SIZE) {
+		curr = skb_dequeue(q);
+		if (curr)
+			kfree_skb(curr);
+	}
+
+	skb_queue_tail(q, skb);
+}
+
+/* ls_queue() - queue packet for delivery if destination is not a neighbour
+ * @dest_addr: destination address
+ * @skb: the packet
+ *
+ * If the destination address is not a known neighbour ẃith recent activity,
+ * queue the packet
+ *
+ * Return: GN_QUEUE_DIRECT if the destination is a neighbour,
+ * GN_QUEUE_LS_PENDING if the destination is unknown, but a LS request is
+ * pending and the packet is queued,
+ * GN_QUEUE_LS_STALE if the destination is unknown or its entry is stale and
+ * we should send a LS query, GN_QUEUE_ERROR on error
+ */
+int gn_ls_queue(gn_address_t dest_addr, struct sk_buff *skb)
+{
+	struct loc_te *entry;
+	int rc = -ENOENT;
+
+	spin_lock_bh(&gn_loc_t_lock);
+	hash_for_each_possible(gn_loc_t, entry, hnode, dest_addr) {
+		if (entry->addr != dest_addr)
+			continue;
+
+		if (GN_TST_VALID(entry->tst_addr)) {
+			// Entry is valid, no need for LS
+			rc = GN_QUEUE_DIRECT;
+		} else if (entry->ls_pending == 1) {
+			// Entry is stale, but we already sent a LS request
+			rc = GN_QUEUE_LS_PENDING;
+			__ls_queue(&entry->lsb, skb);
+		} else {
+			// Entry is stale, perform LS request
+			rc = GN_QUEUE_LS_STALE;
+
+			entry->ls_pending = 1;
+			__ls_queue(&entry->lsb, skb);
+		}
+
+		break;
+	}
+	if (rc == -ENOENT) {
+		entry = kzalloc_obj(*entry, GFP_ATOMIC);
+		if (!entry) {
+			spin_unlock_bh(&gn_loc_t_lock);
+			return -ENOMEM;
+		}
+
+		rc = GN_QUEUE_LS_STALE;
+		entry->addr = dest_addr;
+		entry->ls_pending = 1;
+		skb_queue_head_init(&entry->lsb);
+		__ls_queue(&entry->lsb, skb);
+
+		hash_add(gn_loc_t, &entry->hnode, entry->addr);
+	}
+	spin_unlock_bh(&gn_loc_t_lock);
+
+	return rc;
+}
+
+void gn_ls_flush(gn_address_t dest_addr)
+{
+	struct sk_buff *tmp_skb;
+	u8 ll_address[ETH_ALEN];
+	struct loc_te *entry;
+	bool has_mac = false;
+
+	spin_lock_bh(&gn_loc_t_lock);
+	hash_for_each_possible(gn_loc_t, entry, hnode, dest_addr) {
+		if (entry->addr != dest_addr)
+			continue;
+		if (!entry->ls_pending)
+			break;
+
+		if (entry->is_neighbour &&
+		    !is_zero_ether_addr(entry->ll_address)) {
+			ether_addr_copy(ll_address, entry->ll_address);
+			has_mac = true;
+		}
+
+		while ((tmp_skb = skb_dequeue(&entry->lsb)) != NULL) {
+			struct gn_header *gh =
+				(struct gn_header *)skb_network_header(tmp_skb);
+			struct gn_iface *gnif =
+				gn_find_interface_by_dev(tmp_skb->dev);
+
+			/* Populate DEPV for queued GeoUnicast packets when location is
+			   resolved */
+			if (gh->gc_h.ht == CH_HT_GUC) {
+				gh->guc_h.depv.tst = entry->pv.tst;
+				gh->guc_h.depv.lat = entry->pv.lat;
+				gh->guc_h.depv.lon = entry->pv.lon;
+			}
+
+			if (has_mac)
+				gn_dl->request(gn_dl, tmp_skb, ll_address);
+			else if (gnif && !gn_query_ll_nexthop(gnif, dest_addr,
+							      ll_address))
+				gn_dl->request(gn_dl, tmp_skb, ll_address);
+			else
+				gn_dl->request(gn_dl, tmp_skb,
+					       tmp_skb->dev->broadcast);
+		}
+		entry->ls_pending = 0;
+		break;
+	}
+	spin_unlock_bh(&gn_loc_t_lock);
+}
+
+int gn_query_ll_address(gn_address_t query_addr, u8 *ll_address)
+{
+	struct loc_te *entry;
+	int rc = -ENOENT;
+
+	spin_lock_bh(&gn_loc_t_lock);
+	hash_for_each_possible(gn_loc_t, entry, hnode, query_addr) {
+		if (entry->addr != query_addr)
+			continue;
+		if (!entry->is_neighbour || !GN_TST_VALID(entry->tst_addr))
+			break;
+		if (is_zero_ether_addr(entry->ll_address))
+			break;
+
+		rc = 0;
+		ether_addr_copy(ll_address, entry->ll_address);
+		break;
+	}
+	spin_unlock_bh(&gn_loc_t_lock);
+
+	return rc;
+}
+
+/**
+ * gn_query_ll_nexthop - Query linklayer address or next-hop for GUC forwarding
+ * @gnif: Local GeoNetworking interface sending the packet
+ * @query_addr: Destination GeoNetworking address
+ * @ll_address: Buffer to receive the linklayer (MAC) address
+ *
+ * If query_addr is a direct 1-hop neighbor, resolves directly to its MAC
+ * address.
+ * If query_addr is a multi-hop destination in LocTE, runs greedy forwarding
+ * to select the best next-hop neighbor toward the destination.
+ *
+ * Return: 0 if linklayer address resolved, negative errno if broadcast needed.
+ */
+int gn_query_ll_nexthop(struct gn_iface *gnif, gn_address_t query_addr,
+			u8 *ll_address)
+{
+	bool is_neighbor = false;
+	struct gn_lpv target_pv;
+	struct loc_te *entry;
+	bool found = false;
+
+	spin_lock_bh(&gn_loc_t_lock);
+	hash_for_each_possible(gn_loc_t, entry, hnode, query_addr) {
+		if (entry->addr != query_addr)
+			continue;
+		if (!GN_TST_VALID(entry->tst_addr))
+			break;
+		if (entry->is_neighbour &&
+		    !is_zero_ether_addr(entry->ll_address)) {
+			ether_addr_copy(ll_address, entry->ll_address);
+			is_neighbor = true;
+		} else {
+			target_pv = entry->pv;
+		}
+		found = true;
+		break;
+	}
+	spin_unlock_bh(&gn_loc_t_lock);
+
+	if (!found)
+		return -ENOENT;
+	if (is_neighbor)
+		return 0;
+
+	return (greedy_forward(gnif, ll_address, &target_pv) ==
+		GN_FORWARD_NEXT_HOP) ?
+		       0 :
+		       -EHOSTUNREACH;
+}
+
+/**
+ * gn_fill_depv - Populate Destination Position Vector (DEPV) from Location Table
+ * @depv: Pointer to gn_spv struct to populate
+ * @dest_addr: GeoNetworking address of the destination
+ *
+ * Return: 0 if valid destination position found in LocTE,
+ * negative error code otherwise.
+ */
+int gn_fill_depv(struct gn_spv *depv, gn_address_t dest_addr)
+{
+	struct loc_te *entry;
+	int rc = -ENOENT;
+
+	spin_lock_bh(&gn_loc_t_lock);
+	hash_for_each_possible(gn_loc_t, entry, hnode, dest_addr) {
+		if (entry->addr != dest_addr)
+			continue;
+		if (GN_TST_VALID(entry->tst_addr)) {
+			depv->addr = entry->pv.addr;
+			depv->tst = entry->pv.tst;
+			depv->lat = entry->pv.lat;
+			depv->lon = entry->pv.lon;
+			rc = 0;
+		}
+		break;
+	}
+	spin_unlock_bh(&gn_loc_t_lock);
+
+	return rc;
+}
+
+void gn_routing_exit(void)
+{
+	struct hlist_node *tmp;
+	struct loc_te *entry;
+	int bucket;
+
+	spin_lock_bh(&gn_loc_t_lock);
+	hash_for_each_safe(gn_loc_t, bucket, tmp, entry, hnode) {
+		skb_queue_purge(&entry->lsb);
+		hash_del(&entry->hnode);
+		kfree(entry);
+	}
+	spin_unlock_bh(&gn_loc_t_lock);
+}
diff --git a/net/gn/sysctl_net_gn.c b/net/gn/sysctl_net_gn.c
new file mode 100644
index 000000000000..12bb2cb64135
--- /dev/null
+++ b/net/gn/sysctl_net_gn.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * sysctl_net_gn.c: sysctl interface to net GeoNetworking subsystem
+ */
+
+#include <linux/sysctl.h>
+#include <net/sock.h>
+#include <linux/gn.h>
+
+static int dummy_var = 1234;
+
+static struct ctl_table gn_table[] = { {
+	.procname = "gn-dummy-var",
+	.data = &dummy_var,
+	.maxlen = sizeof(int),
+	.mode = 0644,
+	.proc_handler = proc_dointvec,
+} };
+
+static struct ctl_table_header *gn_table_header;
+
+int __init gn_register_sysctl(void)
+{
+	gn_table_header = register_net_sysctl(&init_net, "net/gn", gn_table);
+	if (!gn_table_header)
+		return -ENOMEM;
+	return 0;
+}
+
+void gn_unregister_sysctl(void)
+{
+	unregister_net_sysctl_table(gn_table_header);
+}
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 1a713d96206f..38876505cec3 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -1318,7 +1318,10 @@ static inline u16 socket_type_to_security_class(int family, int type, int protoc
 			return SECCLASS_XDP_SOCKET;
 		case PF_MCTP:
 			return SECCLASS_MCTP_SOCKET;
-#if PF_MAX > 46
+		case PF_GN:
+			return SECCLASS_GN_SOCKET;
+
+#if PF_MAX > 47
 #error New address family defined, please update this function.
 #endif
 		}
diff --git a/security/selinux/include/classmap.h b/security/selinux/include/classmap.h
index 90cb61b16425..dfa07a586060 100644
--- a/security/selinux/include/classmap.h
+++ b/security/selinux/include/classmap.h
@@ -174,6 +174,7 @@ const struct security_class_mapping secclass_map[] = {
 	    "map_create_as", "prog_load_as", NULL } },
 	{ "xdp_socket", { COMMON_SOCK_PERMS, NULL } },
 	{ "mctp_socket", { COMMON_SOCK_PERMS, NULL } },
+	{ "gn_socket",	{ COMMON_SOCK_PERMS, NULL } },
 	{ "perf_event",
 	  { "open", "cpu", "kernel", "tracepoint", "read", "write", NULL } },
 	{ "anon_inode", { COMMON_FILE_PERMS, NULL } },
@@ -187,7 +188,7 @@ const struct security_class_mapping secclass_map[] = {
 #ifdef __KERNEL__ /* avoid this check when building host programs */
 #include <linux/socket.h>
 
-#if PF_MAX > 46
+#if PF_MAX > 47
 #error New address family defined, please update secclass_map.
 #endif
 #endif
-- 
2.55.0


^ permalink raw reply related

* [RFC PATCH net-next v0.1 0/1] add GeoNetworking protocol
From: Simon Dietz @ 2026-07-18 21:00 UTC (permalink / raw)
  To: netdev
  Cc: andrew+netdev, davem, edumazet, johannes, kuniyu, linux-wireless,
	simon.dietz, dietz23838

Implement the GeoNetworking / ETSI ITS-G5 ('net/gn') protocol which
is based on 802.11p wifi and used for vehicle2x applications. It is
standardized by the ETSI and used by some car manufacturers
(especially in europe). It enables ad-hoc, multi-hop geographical
communication and routing among vehicles (and road- or railside
infrastructure).

Most work of this implementation has been done by the bachelor
project 2018/2019 of the operating systems and middleware group of
the Hasso Plattner Institute, University of Potsdam, which the author
was part of.

The code [1] is published under the GPL v2 at this location:
https://gitlab.com/hpi-potsdam/osm/g5-on-linux/linux-geonetworking/

This patch set is not merge-ready, yet. E.g. network namespaces are
not supported at all, documentation is missing, ...

Purpose of this RFC is to determine if GeoNetworking support in the
linux kernel is desirable and if so what steps would have to be done
next.

In the original implementation new ioctl calls have been used. For the
removal of them and changing their functionality to use netlink (and
only this change) help of AI has been used as stated in the commit.

Changes since v0:
* replace ioctl interface changes with netlink
* remove profcs
* remove magic value in unix/ETSI epoch conversion
* add _HLEN macros
* remove inline from static functions
* remove commented out code
* change default log level to debug
* adhere to line limit of 80

Changes in v0:
* rebase to current-net next
* fix (most) of coding style violations
* fix SPDX-License Identifiers
* use kzalloc_obj instead of kzalloc
* change __attribute__((packed)) to __packed
* (partially?) fix locking
* adhere to kernel error code conventions
* add various bounds, 0, NULL, ... checks
* use has_for_each_safe
* refactor forward_packet helper functions
* document -EEXIST return on existing dev
* add gn_fill_depv and ls_flush logic
* add nexthop query logic
* replace timespec64 with uapi conform __kernel_timespec
* fix default hop limit

Detailed explaination of the changes:

net: gn: remove procfs

Remove any procfs usage for debugging purposes. Procfs has been used
during the development of the gn module for passing gps data from the
user space to the kernel. This behaviour has been changed to ioctl.
Procfs has only been kept for debugging reasons and is removed hereby.

net: gn: remove magic value in unix/ETSI epoc conversion

ETSI uses a different epoch than the unix default 1970-01-01 00:00:00.
For epoch conversion a magic value has been used, which now is replaced
by a constant.

net: gn: add _HLEN macros

Add _HLEN macros and use them to shorten code lines.

net: gn: remove inline from static functions
net: gn: remove commented out code
net: gn: change default log level to debug
net: gn: enforce line lenght limit of 80

For an explaination of changes in v0 compared to the original code base
[1] consult the patch series v0.

Greetings,
Simon

PS: The commit message date is off, because I squashed the commits.

Simon Dietz (1):
  net: add GeoNetworking protocol

 include/linux/gn.h                  |  363 +++++
 include/linux/gn_routing.h          |   68 +
 include/linux/socket.h              |    6 +-
 include/uapi/linux/gn.h             |   84 ++
 include/uapi/linux/if_ether.h       |    1 +
 include/uapi/linux/if_link.h        |   10 +
 net/Kconfig                         |    2 +
 net/Makefile                        |    1 +
 net/gn/Kconfig                      |    4 +
 net/gn/Makefile                     |    7 +
 net/gn/gn_netlink.c                 |  266 ++++
 net/gn/gn_prot.c                    | 1908 +++++++++++++++++++++++++++
 net/gn/gn_routing.c                 |  646 +++++++++
 net/gn/sysctl_net_gn.c              |   33 +
 security/selinux/hooks.c            |    5 +-
 security/selinux/include/classmap.h |    3 +-
 16 files changed, 3403 insertions(+), 4 deletions(-)
 create mode 100644 include/linux/gn.h
 create mode 100644 include/linux/gn_routing.h
 create mode 100644 include/uapi/linux/gn.h
 create mode 100644 net/gn/Kconfig
 create mode 100644 net/gn/Makefile
 create mode 100644 net/gn/gn_netlink.c
 create mode 100644 net/gn/gn_prot.c
 create mode 100644 net/gn/gn_routing.c
 create mode 100644 net/gn/sysctl_net_gn.c

-- 
2.55.0


^ permalink raw reply

* Re: unix_stream_connect and socket address resolution
From: David Laight @ 2026-07-18 20:58 UTC (permalink / raw)
  To: John Ericson
  Cc: Kuniyuki Iwashima, David S . Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Cong Wang, Simon Horman, Christian Brauner,
	David Rheinsberg, Andy Lutomirski, Sergei Zimmerman, network dev,
	Mickaël Salaün, Günther Noack, Paul Moore,
	linux-security-module, LKML
In-Reply-To: <e6f54c5b-2c92-4772-b2f9-5c21e2ec50a9@app.fastmail.com>

On Sat, 18 Jul 2026 15:55:12 -0400
"John Ericson" <mail@johnericson.me> wrote:

> In [1] I observed what I considered some odd behavior in unix_stream_connect():
> 
> > I was hoping this was going to be a simple matter of factoring out the
> > back half of `unix_stream_connect`. No such luck was had, because
> > actually instead of `unix_stream_connect` looking up the socket from the
> > VFS once, it does it repeatedly in the same loop that is used to deal
> > with full listening queues.
> > 
> > (This behavior is rather surprising to me, because it would allow a
> > deleted and recreated socket to be picked up on the next loop iteration.
> > But, I don't want to make any UAPI-visible changes in this patch series,
> > so I did not consider changing it.)  
> 
> I had said I didn't want to consider changing this yet in my patch
> series, but based on the feedback I received for a second version of
> that patch series, I now actually think it is a good idea after all to
> discuss this first, and see if it should be changed prior to my patch
> series. (This discussion will inform what the code looks like before I
> do my v2 patch series, and how big or small that patch series is.)
> 
> Here are two scenarios where the current behavior of
> `unix_stream_connect` is surprising:
> 
> File system version:
> 
> 1. server binds socket `/foo/bar`
> 
> 2. clients fill up the accept queue, begin looping
> 
> 3. `mv /foo /foo2; mkdir /foo`
> 
> 4. another server binds `/foo/bar`
> 
> 5. clients connect to the second server instead
> 
> The loop in question is within `unix_stream_connect` itself, not in
> user code. I consider it very surprising that `/foo/bar` is looked up
> multiple times during a single system call.
> 
> Abstract socket version:
> 
> 1. server binds abstract socket `@foo`
> 
> 2. clients fill up the accept queue, begin looping
> 
> 3. server closes its socket (or exits), releasing the abstract name
> 
> 4. another server binds `@foo`
> 
> 5. clients connect to the second server instead
> 
> For abstract sockets we cannot play tricks with `mv`: the first server
> does need to relinquish `@foo` itself. But still, the result is the same
> where a different socket is resolved on the next loop iteration in
> `unix_stream_connect`. I am not sure it is fair to call this a TOCTOU
> issue exactly, but it feels very similar to one.
> 
> The more natural semantics in my view would be to first resolve the
> address to a socket, and then loop holding that resolved socket
> constant. With these semantics:
> 
> - In the `mv` case, the retrying clients continue to try connecting to
>   the original socket, now at `/foo2/bar`.
> 
> - In the close case, the retrying clients fail, and do not connect to
>   any new socket at the same path or abstract name.
> 
> What do you all think? Is this better? If so, is this a security fix
> which can be made unconditionally, or, absent a real concrete attack
> vector, is this a UAPI-breaking change which is automatically out of
> scope, and would thus need an explicit opt-in mechanism?
> 
> Looking forward to feedback,

My $0.02

If you assume that the client isn't responsible for restarting the server,
then there is no strong timing relation between creating a new server
(by any means) and the connect request from the client.
In other words both the above are very similar to the client being
preempted at the start of the connect() system call.

What you need to do is hard link foo to foo1, create the new
socket at foo2/bar then mv foo2 to foo so that it is atomic.
But I suspect hard links to directories aren't allowed any more :-(
(Creating 'random' hard links to directories used to be 'fun',
you could get 'find' in a right mess.)

	David


> 
> John
> 
> [1]: https://lore.kernel.org/all/20260703073948.2541875-3-John.Ericson@Obsidian.Systems/
> 


^ permalink raw reply

* unix_stream_connect and socket address resolution
From: John Ericson @ 2026-07-18 19:55 UTC (permalink / raw)
  To: Kuniyuki Iwashima, David S . Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni
  Cc: Cong Wang, Simon Horman, Christian Brauner, David Rheinsberg,
	Andy Lutomirski, Sergei Zimmerman, network dev,
	Mickaël Salaün, Günther Noack, Paul Moore,
	linux-security-module, LKML
In-Reply-To: <20260703073948.2541875-3-John.Ericson@Obsidian.Systems>

In [1] I observed what I considered some odd behavior in unix_stream_connect():

> I was hoping this was going to be a simple matter of factoring out the
> back half of `unix_stream_connect`. No such luck was had, because
> actually instead of `unix_stream_connect` looking up the socket from the
> VFS once, it does it repeatedly in the same loop that is used to deal
> with full listening queues.
> 
> (This behavior is rather surprising to me, because it would allow a
> deleted and recreated socket to be picked up on the next loop iteration.
> But, I don't want to make any UAPI-visible changes in this patch series,
> so I did not consider changing it.)

I had said I didn't want to consider changing this yet in my patch
series, but based on the feedback I received for a second version of
that patch series, I now actually think it is a good idea after all to
discuss this first, and see if it should be changed prior to my patch
series. (This discussion will inform what the code looks like before I
do my v2 patch series, and how big or small that patch series is.)

Here are two scenarios where the current behavior of
`unix_stream_connect` is surprising:

File system version:

1. server binds socket `/foo/bar`

2. clients fill up the accept queue, begin looping

3. `mv /foo /foo2; mkdir /foo`

4. another server binds `/foo/bar`

5. clients connect to the second server instead

The loop in question is within `unix_stream_connect` itself, not in
user code. I consider it very surprising that `/foo/bar` is looked up
multiple times during a single system call.

Abstract socket version:

1. server binds abstract socket `@foo`

2. clients fill up the accept queue, begin looping

3. server closes its socket (or exits), releasing the abstract name

4. another server binds `@foo`

5. clients connect to the second server instead

For abstract sockets we cannot play tricks with `mv`: the first server
does need to relinquish `@foo` itself. But still, the result is the same
where a different socket is resolved on the next loop iteration in
`unix_stream_connect`. I am not sure it is fair to call this a TOCTOU
issue exactly, but it feels very similar to one.

The more natural semantics in my view would be to first resolve the
address to a socket, and then loop holding that resolved socket
constant. With these semantics:

- In the `mv` case, the retrying clients continue to try connecting to
  the original socket, now at `/foo2/bar`.

- In the close case, the retrying clients fail, and do not connect to
  any new socket at the same path or abstract name.

What do you all think? Is this better? If so, is this a security fix
which can be made unconditionally, or, absent a real concrete attack
vector, is this a UAPI-breaking change which is automatically out of
scope, and would thus need an explicit opt-in mechanism?

Looking forward to feedback,

John

[1]: https://lore.kernel.org/all/20260703073948.2541875-3-John.Ericson@Obsidian.Systems/

^ permalink raw reply

* [PATCH net] rds: tcp: unregister sysctl before tearing down listen socket
From: Cen Zhang (Microsoft) @ 2026-07-18 18:34 UTC (permalink / raw)
  To: achender
  Cc: davem, edumazet, kuba, pabeni, horms, netdev, linux-rdma,
	rds-devel, linux-kernel, AutonomousCodeSecurity, tgopinath, kys,
	blbllhy

rds_tcp_exit_net() frees the per-netns RDS TCP listen socket via
rds_tcp_kill_sock() before unregistering the per-netns sysctl table.  Since
rds_tcp_skbuf_handler() derives the netns from rtn->rds_tcp_listen_sock->sk,
a concurrent sysctl write can race with netns teardown and dereference the
freed socket/sk.

KASAN reports the race as:

  BUG: KASAN: slab-use-after-free in rds_tcp_skbuf_handler+0x2aa/0x2e0
  rds_tcp_skbuf_handler              net/rds/tcp.c:721
  proc_sys_call_handler              fs/proc/proc_sysctl.c
  vfs_write                          fs/read_write.c
  __x64_sys_pwrite64                 fs/read_write.c

Fix this by unregistering the RDS TCP sysctl table before calling
rds_tcp_kill_sock().  unregister_net_sysctl_table() prevents new sysctl
handlers from starting and waits for in-flight handlers to finish, so
the listen socket can then be released safely.

Fixes: 7f5611cbc487 ("rds: sysctl: rds_tcp_{rcv,snd}buf: avoid using current->nsproxy")
Reported-by: AutonomousCodeSecurity@microsoft.com
Signed-off-by: Cen Zhang (Microsoft) <blbllhy@gmail.com>
---
 net/rds/tcp.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/net/rds/tcp.c b/net/rds/tcp.c
index a1de114d5e2e..453d4077a85e 100644
--- a/net/rds/tcp.c
+++ b/net/rds/tcp.c
@@ -655,13 +655,13 @@ static void __net_exit rds_tcp_exit_net(struct net *net)
 {
 	struct rds_tcp_net *rtn = net_generic(net, rds_tcp_netid);
 
-	rds_tcp_kill_sock(net);
-
 	if (rtn->rds_tcp_sysctl)
 		unregister_net_sysctl_table(rtn->rds_tcp_sysctl);
 
 	if (net != &init_net)
 		kfree(rtn->ctl_table);
+
+	rds_tcp_kill_sock(net);
 }
 
 static struct pernet_operations rds_tcp_net_ops = {
-- 
2.53.0


^ permalink raw reply related

* [PATCH net v3 2/2] selftests/net/af_unix: test listen() rejects wrong socket states
From: John Ericson @ 2026-07-18 18:29 UTC (permalink / raw)
  To: Kuniyuki Iwashima, David S . Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni
  Cc: John Ericson, Simon Horman, Christian Brauner, David Rheinsberg,
	Cong Wang, Sergei Zimmerman, netdev, linux-kernel
In-Reply-To: <20260718182903.2295560-1-John.Ericson@Obsidian.Systems>

From: John Ericson <mail@johnericson.me>

Add a regression test for the unix_listen() state check. The key case is
listen() on a bound socket that has already been connected: it is no
longer in TCP_CLOSE or TCP_LISTEN, so it must fail with EINVAL. A
prepare_peercred() call slipped in ahead of that check once left err at 0
and made listen() silently succeed there instead; this guards against a
repeat.

The neighbouring outcomes are covered too so they cannot regress the same
way: a bound socket in TCP_CLOSE listens fine, calling listen() again on a
socket already in TCP_LISTEN is allowed, and an unbound socket fails with
EINVAL.

Each case runs for both listenable socket types (SOCK_STREAM and
SOCK_SEQPACKET) and both pathname and abstract addresses.

Fixes: fd0a109a0f6b ("net, pidfs: prepare for handing out pidfds for reaped sk->sk_peer_pid")
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: John Ericson <mail@johnericson.me>
---
 .../testing/selftests/net/af_unix/.gitignore  |   1 +
 tools/testing/selftests/net/af_unix/Makefile  |   1 +
 .../selftests/net/af_unix/unix_listen.c       | 187 ++++++++++++++++++
 3 files changed, 189 insertions(+)
 create mode 100644 tools/testing/selftests/net/af_unix/unix_listen.c

diff --git a/tools/testing/selftests/net/af_unix/.gitignore b/tools/testing/selftests/net/af_unix/.gitignore
index 240b26740c9e..973176644103 100644
--- a/tools/testing/selftests/net/af_unix/.gitignore
+++ b/tools/testing/selftests/net/af_unix/.gitignore
@@ -6,3 +6,4 @@ scm_rights
 so_peek_off
 unix_connect
 unix_connreset
+unix_listen
diff --git a/tools/testing/selftests/net/af_unix/Makefile b/tools/testing/selftests/net/af_unix/Makefile
index 4c0375e28bbe..57d159803a3a 100644
--- a/tools/testing/selftests/net/af_unix/Makefile
+++ b/tools/testing/selftests/net/af_unix/Makefile
@@ -14,6 +14,7 @@ TEST_GEN_PROGS := \
 	so_peek_off \
 	unix_connect \
 	unix_connreset \
+	unix_listen \
 # end of TEST_GEN_PROGS
 
 include ../../lib.mk
diff --git a/tools/testing/selftests/net/af_unix/unix_listen.c b/tools/testing/selftests/net/af_unix/unix_listen.c
new file mode 100644
index 000000000000..416fa3e5bfe9
--- /dev/null
+++ b/tools/testing/selftests/net/af_unix/unix_listen.c
@@ -0,0 +1,187 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Tests for the state checks in AF_UNIX listen().
+ *
+ * The central case is a regression test: listen() on a bound socket that
+ * is already connected (i.e. not in TCP_CLOSE or TCP_LISTEN state) must
+ * fail with EINVAL.  A prior change accidentally let it return success
+ * without doing anything, because a helper called in between reset the
+ * error code to 0.  The neighbouring checks (unbound, already listening)
+ * are tested too so they cannot silently regress the same way.
+ *
+ * Every case runs for both listenable socket types (SOCK_STREAM and
+ * SOCK_SEQPACKET) and both pathname and abstract addresses.
+ */
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "kselftest_harness.h"
+
+#define SK_NAME		"unix_listen_sk"
+#define SRV_NAME	"unix_listen_srv"
+
+FIXTURE(unix_listen)
+{
+	int sk;			/* socket under test */
+	int server;		/* a listening peer, when a test needs one */
+	struct sockaddr_un addr, srv_addr;
+	socklen_t addrlen, srv_addrlen;
+};
+
+FIXTURE_VARIANT(unix_listen)
+{
+	int type;
+	int abstract;
+};
+
+FIXTURE_VARIANT_ADD(unix_listen, stream_pathname)
+{
+	.type = SOCK_STREAM,
+	.abstract = 0,
+};
+
+FIXTURE_VARIANT_ADD(unix_listen, stream_abstract)
+{
+	.type = SOCK_STREAM,
+	.abstract = 1,
+};
+
+FIXTURE_VARIANT_ADD(unix_listen, seqpacket_pathname)
+{
+	.type = SOCK_SEQPACKET,
+	.abstract = 0,
+};
+
+FIXTURE_VARIANT_ADD(unix_listen, seqpacket_abstract)
+{
+	.type = SOCK_SEQPACKET,
+	.abstract = 1,
+};
+
+/* Fill @addr with a pathname or abstract address named @name. */
+static socklen_t unix_set_addr(struct sockaddr_un *addr, const char *name,
+			       int abstract)
+{
+	size_t len = strlen(name);
+
+	memset(addr, 0, sizeof(*addr));
+	addr->sun_family = AF_UNIX;
+	/* An abstract address leads with a NUL and has no filesystem entry. */
+	memcpy(addr->sun_path + (abstract ? 1 : 0), name, len);
+
+	return offsetof(struct sockaddr_un, sun_path) + len + 1;
+}
+
+FIXTURE_SETUP(unix_listen)
+{
+	self->sk = -1;
+	self->server = -1;
+	self->addrlen = unix_set_addr(&self->addr, SK_NAME, variant->abstract);
+	self->srv_addrlen = unix_set_addr(&self->srv_addr, SRV_NAME,
+					  variant->abstract);
+}
+
+FIXTURE_TEARDOWN(unix_listen)
+{
+	if (self->sk >= 0)
+		close(self->sk);
+	if (self->server >= 0)
+		close(self->server);
+
+	/* Pathname sockets leave a filesystem entry behind; abstract ones do not. */
+	if (!variant->abstract) {
+		remove(SK_NAME);
+		remove(SRV_NAME);
+	}
+}
+
+/* A bound socket in TCP_CLOSE is the normal, allowed case. */
+TEST_F(unix_listen, bound_is_ok)
+{
+	int err;
+
+	self->sk = socket(AF_UNIX, variant->type, 0);
+	ASSERT_LE(0, self->sk);
+
+	err = bind(self->sk, (struct sockaddr *)&self->addr, self->addrlen);
+	ASSERT_EQ(0, err);
+
+	err = listen(self->sk, 8);
+	EXPECT_EQ(0, err);
+}
+
+/* Listening again on an already-listening socket (TCP_LISTEN) is allowed. */
+TEST_F(unix_listen, relisten_is_ok)
+{
+	int err;
+
+	self->sk = socket(AF_UNIX, variant->type, 0);
+	ASSERT_LE(0, self->sk);
+
+	err = bind(self->sk, (struct sockaddr *)&self->addr, self->addrlen);
+	ASSERT_EQ(0, err);
+
+	err = listen(self->sk, 8);
+	ASSERT_EQ(0, err);
+
+	err = listen(self->sk, 16);
+	EXPECT_EQ(0, err);
+}
+
+/* listen() on an unbound socket fails: there is nothing to listen on. */
+TEST_F(unix_listen, unbound_is_einval)
+{
+	int err;
+
+	self->sk = socket(AF_UNIX, variant->type, 0);
+	ASSERT_LE(0, self->sk);
+
+	err = listen(self->sk, 8);
+	EXPECT_EQ(-1, err);
+	EXPECT_EQ(EINVAL, errno);
+}
+
+/*
+ * The regression: a bound socket that has already been connected is not in
+ * TCP_CLOSE or TCP_LISTEN, so listen() must reject it with EINVAL rather
+ * than quietly succeeding.
+ */
+TEST_F(unix_listen, connected_is_einval)
+{
+	int err;
+
+	self->server = socket(AF_UNIX, variant->type, 0);
+	ASSERT_LE(0, self->server);
+
+	err = bind(self->server, (struct sockaddr *)&self->srv_addr,
+		   self->srv_addrlen);
+	ASSERT_EQ(0, err);
+
+	err = listen(self->server, 8);
+	ASSERT_EQ(0, err);
+
+	self->sk = socket(AF_UNIX, variant->type, 0);
+	ASSERT_LE(0, self->sk);
+
+	/* Bind first so the unbound check does not mask the state check. */
+	err = bind(self->sk, (struct sockaddr *)&self->addr, self->addrlen);
+	ASSERT_EQ(0, err);
+
+	err = connect(self->sk, (struct sockaddr *)&self->srv_addr,
+		      self->srv_addrlen);
+	ASSERT_EQ(0, err);
+
+	err = listen(self->sk, 8);
+	EXPECT_EQ(-1, err);
+	EXPECT_EQ(EINVAL, errno);
+}
+
+TEST_HARNESS_MAIN
-- 
2.54.0


^ permalink raw reply related

* [PATCH net v3 1/2] af_unix: fix listen() succeeding on sockets in the wrong state
From: John Ericson @ 2026-07-18 18:29 UTC (permalink / raw)
  To: Kuniyuki Iwashima, David S . Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni
  Cc: John Ericson, Simon Horman, Christian Brauner, David Rheinsberg,
	Cong Wang, Sergei Zimmerman, netdev, linux-kernel

From: John Ericson <mail@johnericson.me>

Commit fd0a109a0f6b ("net, pidfs: prepare for handing out pidfds for
reaped sk->sk_peer_pid") inserted a prepare_peercred() call between err
= -EINVAL and the socket-state check in unix_listen(). Since
prepare_peercred() leaves err at 0 on success, listen() on an AF_UNIX
socket that is not in TCP_CLOSE or TCP_LISTEN state (e.g. one that is
already connected) now silently returns success without doing anything,
instead of failing with EINVAL as it did before.

Fixes: fd0a109a0f6b ("net, pidfs: prepare for handing out pidfds for reaped sk->sk_peer_pid")
Assisted-by: Claude:claude-fable-5
Signed-off-by: John Ericson <mail@johnericson.me>
---
 net/unix/af_unix.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index f7a9d55eee8a..10ed9421e43a 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -823,6 +823,7 @@ static int unix_listen(struct socket *sock, int backlog)
 	if (err)
 		goto out;
 	unix_state_lock(sk);
+	err = -EINVAL;
 	if (sk->sk_state != TCP_CLOSE && sk->sk_state != TCP_LISTEN)
 		goto out_unlock;
 	if (backlog > sk->sk_max_ack_backlog)
-- 
2.54.0


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox