@@ -1264,6 +1264,11 @@ pub fn run(config: &crate::config::Config) -> crate::Result<()> {
12641264
12651265 // Lock-free shared WG peer address (client mode: egress writes, ingress reads)
12661266 let shared_wg_peer = Arc :: new ( SharedAddr :: new ( ) ) ;
1267+ // Endpoint roaming: ingress writes the actual source addr of the last authenticated
1268+ // packet from the peer; egress prefers this over the configured remote_peer_addr.
1269+ // This lets the server respond to the client's real (possibly ephemeral) port without
1270+ // requiring peer_ip=dynamic.
1271+ let shared_peer_ext = Arc :: new ( SharedAddr :: new ( ) ) ;
12671272
12681273 // Shared maps for dynamic_peer routing (egress reads client_map, ingress writes it; vice versa for session_map)
12691274 let client_map: Arc < Mutex < HashMap < u32 , SocketAddr > > > = Arc :: new ( Mutex :: new ( HashMap :: new ( ) ) ) ;
@@ -1280,6 +1285,7 @@ pub fn run(config: &crate::config::Config) -> crate::Result<()> {
12801285 let egress_session_map = Arc :: clone ( & session_map) ;
12811286 let egress_peer = peer. clone ( ) ;
12821287 let egress_client_obfs = Arc :: clone ( & client_obfs) ;
1288+ let egress_peer_ext = Arc :: clone ( & shared_peer_ext) ;
12831289 let egress_handle = std:: thread:: Builder :: new ( )
12841290 . name ( "gutd-egress" . into ( ) )
12851291 . spawn ( move || {
@@ -1360,7 +1366,9 @@ pub fn run(config: &crate::config::Config) -> crate::Result<()> {
13601366 }
13611367
13621368 let egress_dest = if !dynamic_peer {
1363- remote_peer_addr
1369+ // Prefer the endpoint learned from the last authenticated inbound
1370+ // packet (roaming), fall back to the statically configured address.
1371+ egress_peer_ext. load ( ) . map ( Some ) . unwrap_or ( remote_peer_addr)
13641372 } else if size >= 4 {
13651373 let wg_type = buf[ 0 ] & 0x1F ;
13661374 if wg_type == 1 {
@@ -1517,6 +1525,7 @@ pub fn run(config: &crate::config::Config) -> crate::Result<()> {
15171525 let ingress_client_map = Arc :: clone ( & client_map) ;
15181526 let ingress_session_map = Arc :: clone ( & session_map) ;
15191527 let ingress_client_obfs = Arc :: clone ( & client_obfs) ;
1528+ let ingress_peer_ext = Arc :: clone ( & shared_peer_ext) ;
15201529 let ingress_peer = peer. clone ( ) ;
15211530
15221531 let ingress_handle = std:: thread:: Builder :: new ( )
@@ -1725,6 +1734,11 @@ pub fn run(config: &crate::config::Config) -> crate::Result<()> {
17251734 if let Some ( ( new_size, _wg_sport, _wg_dport) ) =
17261735 obfs_decap ( buf, size, & key_init, rounds, detected_obfs)
17271736 {
1737+ // Endpoint roaming: remember the actual source of every
1738+ // authenticated packet so the egress thread can respond
1739+ // to the peer's real (possibly ephemeral) port.
1740+ ingress_peer_ext. store ( src) ;
1741+
17281742 if dynamic_peer && new_size >= 8 {
17291743 let wg_type = buf[ 0 ] & 0x1F ;
17301744 if wg_type == 1 {
0 commit comments