Skip to content

Commit

Permalink
Add support for setting do-not-fragment bit (linux) (#39)
Browse files Browse the repository at this point in the history
Signed-off-by: Jeremiah Millay <jmillay@fastly.com>
  • Loading branch information
floatingstatic authored May 21, 2023
1 parent 3079a15 commit 235b493
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 0 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ This library supports setting the `SO_MARK` socket option which is equivalent to
flag in standard ping binaries on linux. Setting this option requires the `CAP_NET_ADMIN` capability
(via `setcap` or elevated privileges). You can set a mark (ex: 100) with `pinger.SetMark(100)` in your code.

Setting the "Don't Fragment" bit is supported under Linux which is equivalent to `ping -Mdo`.
You can enable this with `pinger.SetDoNotFragment(true)`.

### Windows

You must use `pinger.SetPrivileged(true)`, otherwise you will receive
Expand Down
1 change: 1 addition & 0 deletions packetconn.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type packetConn interface {
WriteTo(b []byte, dst net.Addr) (int, error)
SetTTL(ttl int)
SetMark(m uint) error
SetDoNotFragment() error
}

type icmpConn struct {
Expand Down
15 changes: 15 additions & 0 deletions ping.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ var (
ipv6Proto = map[string]string{"icmp": "ip6:ipv6-icmp", "udp": "udp6"}

ErrMarkNotSupported = errors.New("setting SO_MARK socket option is not supported on this platform")
ErrDFNotSupported = errors.New("setting do-not-fragment bit is not supported on this platform")
)

// New returns a new Pinger struct pointer.
Expand Down Expand Up @@ -195,6 +196,9 @@ type Pinger struct {
// mark is a SO_MARK (fwmark) set on outgoing icmp packets
mark uint

// df when true sets the do-not-fragment bit in the outer IP or IPv6 header
df bool

// trackerUUIDs is the list of UUIDs being used for sending packets.
trackerUUIDs []uuid.UUID

Expand Down Expand Up @@ -414,6 +418,11 @@ func (p *Pinger) Mark() uint {
return p.mark
}

// SetDoNotFragment sets the do-not-fragment bit in the outer IP header to the desired value.
func (p *Pinger) SetDoNotFragment(df bool) {
p.df = df
}

// Run runs the pinger. This is a blocking function that will exit when it's
// done. If Count or Interval are not specified, it will run continuously until
// it is interrupted.
Expand Down Expand Up @@ -447,6 +456,12 @@ func (p *Pinger) RunWithContext(ctx context.Context) error {
}
}

if p.df {
if err := conn.SetDoNotFragment(); err != nil {
return fmt.Errorf("error setting do-not-fragment: %v", err)
}
}

conn.SetTTL(p.TTL)
return p.run(ctx, conn)
}
Expand Down
1 change: 1 addition & 0 deletions ping_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,7 @@ func (c testPacketConn) SetFlagTTL() error { return nil }
func (c testPacketConn) SetReadDeadline(t time.Time) error { return nil }
func (c testPacketConn) SetTTL(t int) {}
func (c testPacketConn) SetMark(m uint) error { return nil }
func (c testPacketConn) SetDoNotFragment() error { return nil }

func (c testPacketConn) ReadFrom(b []byte) (n int, ttl int, src net.Addr, err error) {
return 0, 0, nil, nil
Expand Down
36 changes: 36 additions & 0 deletions utils_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,42 @@ func (c *icmpV6Conn) SetMark(mark uint) error {
)
}

// SetDoNotFragment sets the do-not-fragment bit in the IP header of outgoing ICMP packets.
func (c *icmpConn) SetDoNotFragment() error {
fd, err := getFD(c.c)
if err != nil {
return err
}
return os.NewSyscallError(
"setsockopt",
syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_MTU_DISCOVER, syscall.IP_PMTUDISC_DO),
)
}

// SetDoNotFragment sets the do-not-fragment bit in the IP header of outgoing ICMP packets.
func (c *icmpv4Conn) SetDoNotFragment() error {
fd, err := getFD(c.icmpConn.c)
if err != nil {
return err
}
return os.NewSyscallError(
"setsockopt",
syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_MTU_DISCOVER, syscall.IP_PMTUDISC_DO),
)
}

// SetDoNotFragment sets the do-not-fragment bit in the IPv6 header of outgoing ICMPv6 packets.
func (c *icmpV6Conn) SetDoNotFragment() error {
fd, err := getFD(c.icmpConn.c)
if err != nil {
return err
}
return os.NewSyscallError(
"setsockopt",
syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_MTU_DISCOVER, syscall.IP_PMTUDISC_DO),
)
}

// getFD gets the system file descriptor for an icmp.PacketConn
func getFD(c *icmp.PacketConn) (uintptr, error) {
v := reflect.ValueOf(c).Elem().FieldByName("c").Elem()
Expand Down
15 changes: 15 additions & 0 deletions utils_other.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,18 @@ func (c *icmpv4Conn) SetMark(mark uint) error {
func (c *icmpV6Conn) SetMark(mark uint) error {
return ErrMarkNotSupported
}

// SetDoNotFragment sets the do-not-fragment bit in the IP header of outgoing ICMP packets.
func (c *icmpConn) SetDoNotFragment() error {
return ErrDFNotSupported
}

// SetDoNotFragment sets the do-not-fragment bit in the IP header of outgoing ICMP packets.
func (c *icmpv4Conn) SetDoNotFragment() error {
return ErrDFNotSupported
}

// SetDoNotFragment sets the do-not-fragment bit in the IPv6 header of outgoing ICMPv6 packets.
func (c *icmpV6Conn) SetDoNotFragment() error {
return ErrDFNotSupported
}
15 changes: 15 additions & 0 deletions utils_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,18 @@ func (c *icmpv4Conn) SetMark(mark uint) error {
func (c *icmpV6Conn) SetMark(mark uint) error {
return ErrMarkNotSupported
}

// SetDoNotFragment sets the do-not-fragment bit in the IP header of outgoing ICMP packets.
func (c *icmpConn) SetDoNotFragment() error {
return ErrDFNotSupported
}

// SetDoNotFragment sets the do-not-fragment bit in the IP header of outgoing ICMP packets.
func (c *icmpv4Conn) SetDoNotFragment() error {
return ErrDFNotSupported
}

// SetDoNotFragment sets the do-not-fragment bit in the IPv6 header of outgoing ICMPv6 packets.
func (c *icmpV6Conn) SetDoNotFragment() error {
return ErrDFNotSupported
}

0 comments on commit 235b493

Please sign in to comment.