Skip to content

Commit 3244b36

Browse files
committed
feat: support setting ICMP source address for outgoing packets
Previously, the source IP for ICMP echo requests was determined by system routing. This change applies the Pinger.Source value—already used when binding the socket—to outgoing control messages as well, allowing users to specify which local address ICMP packets originate from. This is useful on multi-interface systems or when testing routing behavior from specific local addresses. Signed-off-by: Steven Normore <[email protected]>
1 parent 0930ed1 commit 3244b36

File tree

3 files changed

+46
-0
lines changed

3 files changed

+46
-0
lines changed

packetconn.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ type packetConn interface {
2222
SetDoNotFragment() error
2323
SetBroadcastFlag() error
2424
SetIfIndex(ifIndex int)
25+
SetSource(source net.IP)
2526
SetTrafficClass(uint8) error
2627
InstallICMPIDFilter(id int) error
2728
}
@@ -30,6 +31,7 @@ type icmpConn struct {
3031
c *icmp.PacketConn
3132
ttl int
3233
ifIndex int
34+
source net.IP
3335
}
3436

3537
func (c *icmpConn) Close() error {
@@ -44,6 +46,10 @@ func (c *icmpConn) SetIfIndex(ifIndex int) {
4446
c.ifIndex = ifIndex
4547
}
4648

49+
func (c *icmpConn) SetSource(source net.IP) {
50+
c.source = source
51+
}
52+
4753
func (c *icmpConn) SetReadDeadline(t time.Time) error {
4854
return c.c.SetReadDeadline(t)
4955
}
@@ -86,6 +92,13 @@ func (c *icmpv4Conn) WriteTo(b []byte, dst net.Addr) (int, error) {
8692
cm = &ipv4.ControlMessage{IfIndex: c.ifIndex}
8793
}
8894

95+
if c.source != nil {
96+
if cm == nil {
97+
cm = &ipv4.ControlMessage{}
98+
}
99+
cm.Src = c.source
100+
}
101+
89102
return c.c.IPv4PacketConn().WriteTo(b, cm, dst)
90103
}
91104

@@ -131,6 +144,13 @@ func (c *icmpV6Conn) WriteTo(b []byte, dst net.Addr) (int, error) {
131144
cm = &ipv6.ControlMessage{IfIndex: c.ifIndex}
132145
}
133146

147+
if c.source != nil {
148+
if cm == nil {
149+
cm = &ipv6.ControlMessage{}
150+
}
151+
cm.Src = c.source
152+
}
153+
134154
return c.c.IPv6PacketConn().WriteTo(b, cm, dst)
135155
}
136156

ping.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,15 @@ func (p *Pinger) RunWithContext(ctx context.Context) error {
565565
}
566566
conn.SetIfIndex(iface.Index)
567567
}
568+
569+
if p.Source != "" {
570+
ip := net.ParseIP(p.Source)
571+
if ip == nil {
572+
return fmt.Errorf("invalid source address: %s", p.Source)
573+
}
574+
conn.SetSource(ip)
575+
}
576+
568577
return p.run(ctx, conn)
569578
}
570579

ping_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,22 @@ func TestSetInterfaceName(t *testing.T) {
498498
AssertError(t, err, "device not found")
499499
}
500500

501+
func TestSetSource(t *testing.T) {
502+
pinger := New("localhost")
503+
pinger.Count = 1
504+
pinger.Timeout = time.Second
505+
506+
// Set source address
507+
pinger.Source = "127.0.0.1"
508+
err := pinger.Run()
509+
AssertNoError(t, err)
510+
511+
// Set source to invalid IP
512+
pinger.Source = "invalid-ip"
513+
err = pinger.Run()
514+
AssertError(t, err, "invalid source address: invalid-ip")
515+
}
516+
501517
// Test helpers
502518
func makeTestPinger() *Pinger {
503519
pinger := New("127.0.0.1")
@@ -679,6 +695,7 @@ func (c testPacketConn) SetDoNotFragment() error { return nil }
679695
func (c testPacketConn) SetBroadcastFlag() error { return nil }
680696
func (c testPacketConn) InstallICMPIDFilter(id int) error { return nil }
681697
func (c testPacketConn) SetIfIndex(ifIndex int) {}
698+
func (c testPacketConn) SetSource(source net.IP) {}
682699
func (c testPacketConn) SetTrafficClass(uint8) error { return nil }
683700

684701
func (c testPacketConn) ReadFrom(b []byte) (n int, ttl int, src net.Addr, err error) {

0 commit comments

Comments
 (0)