@@ -74,6 +74,8 @@ type Endpoint struct {
7474 ipv4TOS uint8
7575 // +checklocks:mu
7676 ipv6TClass uint8
77+ // +checklocks:mu
78+ pmtud tcpip.PMTUDStrategy
7779
7880 // Lock ordering: mu > infoMu.
7981 infoMu sync.RWMutex `state:"nosave"`
@@ -234,6 +236,7 @@ type WriteContext struct {
234236 route * stack.Route
235237 ttl uint8
236238 tos uint8
239+ df bool
237240}
238241
239242func (c * WriteContext ) MTU () uint32 {
@@ -362,6 +365,7 @@ func (c *WriteContext) WritePacket(pkt *stack.PacketBuffer, headerIncluded bool)
362365 Protocol : c .e .transProto ,
363366 TTL : c .ttl ,
364367 TOS : c .tos ,
368+ DF : c .df ,
365369 ExperimentOptionValue : expOptVal ,
366370 }, pkt )
367371
@@ -568,11 +572,28 @@ func (e *Endpoint) AcquireContextForWrite(opts tcpip.WriteOptions) (WriteContext
568572 panic (fmt .Sprintf ("invalid protocol number = %d" , netProto ))
569573 }
570574
575+ // Set the DF (Don't Fragment) bit based on the PMTUD strategy,
576+ // matching TCP behavior in connect.go.
577+ // Note: In gVisor, WANT and DO are treated identically (both set DF).
578+ // Linux kernel differentiates them (WANT allows local fragmentation,
579+ // DO returns EMSGSIZE), but gVisor's IPv4 layer always allows local
580+ // fragmentation for locally-generated packets regardless of DF
581+ // (see gvisor.dev/issue/5919).
582+ //
583+ // PROBE also sets DF, matching Linux ip_dont_fragment(). In Linux,
584+ // PROBE differs from DO only in that it ignores incoming ICMP
585+ // "Fragmentation Needed" messages (i.e. does not update the cached
586+ // route PMTU). Since gVisor does not implement ICMP-based PMTU
587+ // feedback for transport sockets, PROBE and DO are functionally
588+ // equivalent here.
589+ df := e .pmtud == tcpip .PMTUDiscoveryWant || e .pmtud == tcpip .PMTUDiscoveryDo || e .pmtud == tcpip .PMTUDiscoveryProbe
590+
571591 return WriteContext {
572592 e : e ,
573593 route : route ,
574594 ttl : ttl ,
575595 tos : tos ,
596+ df : df ,
576597 }, nil
577598}
578599
@@ -840,9 +861,18 @@ func (e *Endpoint) GetRemoteAddress() (tcpip.FullAddress, bool) {
840861func (e * Endpoint ) SetSockOptInt (opt tcpip.SockOptInt , v int ) tcpip.Error {
841862 switch opt {
842863 case tcpip .MTUDiscoverOption :
843- // Return not supported if the value is not disabling path
844- // MTU discovery.
845- if tcpip .PMTUDStrategy (v ) != tcpip .PMTUDiscoveryDont {
864+ // Store PMTU discovery settings. The DF bit on outgoing
865+ // packets is set accordingly in AcquireContextForWrite.
866+ // PROBE is accepted alongside DO/WANT/DONT. In Linux,
867+ // PROBE sets DF but ignores ICMP-based PMTU updates;
868+ // since gVisor lacks ICMP PMTU feedback, it behaves
869+ // identically to DO.
870+ switch tcpip .PMTUDStrategy (v ) {
871+ case tcpip .PMTUDiscoveryWant , tcpip .PMTUDiscoveryDont , tcpip .PMTUDiscoveryDo , tcpip .PMTUDiscoveryProbe :
872+ e .mu .Lock ()
873+ e .pmtud = tcpip .PMTUDStrategy (v )
874+ e .mu .Unlock ()
875+ default :
846876 return & tcpip.ErrNotSupported {}
847877 }
848878
@@ -891,8 +921,10 @@ func (e *Endpoint) SetSockOptInt(opt tcpip.SockOptInt, v int) tcpip.Error {
891921func (e * Endpoint ) GetSockOptInt (opt tcpip.SockOptInt ) (int , tcpip.Error ) {
892922 switch opt {
893923 case tcpip .MTUDiscoverOption :
894- // The only supported setting is path MTU discovery disabled.
895- return int (tcpip .PMTUDiscoveryDont ), nil
924+ e .mu .Lock ()
925+ v := int (e .pmtud )
926+ e .mu .Unlock ()
927+ return v , nil
896928
897929 case tcpip .MulticastTTLOption :
898930 e .mu .Lock ()
0 commit comments