Skip to content

Conversation

@lroehrs
Copy link

@lroehrs lroehrs commented Nov 19, 2025

Summary

This PR adds the ability to bind ICMP sockets (only on linux) to specific network interfaces.

Changes

Core Implementation

  • Sockets: Added interface parameter to ICMPSocket, ICMPv4Socket, and ICMPv6Socket classes

    • Implements binding via SO_BINDTODEVICE socket option on Unix systems (requires root)
    • Automatically ignored on Windows for cross-platform compatibility
  • Functions: Added interface parameter to all public functions:

    • ping()
    • multiping()
    • traceroute()
    • async_ping()
    • async_multiping()

Documentation

Updated:

  • API Documentation
  • README
  • CHANGELOG
  • Example script

Use Cases

Our infrastructure nodes have private and public interfaces. We perform end-to-end testing to ensure that the private interface is functioning correctly; if not, we configure the routes to use the external interface instead. Without interface binding, icmplib always uses the working interface, making it impossible to test a specific interface independently.

This will also fix:

Platform Support

  • Linux: Full support via SO_BINDTODEVICE
  • macOS: Parameter accepted but ignored
  • Windows: Parameter accepted but ignored

Example Usage

from icmplib import ping

# Bind to loopback interface
host = ping('127.0.0.1', interface='lo')

# Bind to ethernet interface
host = ping('8.8.8.8', interface='eth0')

Backward Compatibility

This change is fully backward compatible. The interface parameter is optional and defaults to None.

Testing

  • Tested on Linux (Ubuntu 24.04) with various network interfaces

Add interface parameter to bind ICMP sockets to a specific interface Implement Unix-only binding via SO_BINDTODEVICE in ICMPSocket Propagate interface through
ping, multiping, async_ping, traceroute Update docs and examples to demonstrate interface usage
Update docs, examples, and code to reflect that binding to a network interface is supported only on Linux. Bind operations are skipped on macOS and Windows.
_ICMP_ECHO_REPLY = -1

def __init__(self, address=None, privileged=True):
def __init__(self, address=None, privileged=True, interface=None):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: you can force keyword arguments by inserting an asterisk.

Suggested change
def __init__(self, address=None, privileged=True, interface=None):
def __init__(self, address=None, *, privileged=True, interface=None):

It makes it easier to read code by preventing someone to write ICMPSocket(address, False), instead forcing ICMPSocket(address, privilegied=False).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, cool! I didn't know about that function.
I also prefer it when the keyword is included; it's annoying to have to check what it stands for or be forced to set it.

Since the whole library doesn't use it, would it be unclean to only use it here? And it would be a breaking change.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it's a breaking change so should start with internal functions and only do it for newly added keywords.

Copy link

@kalvdans kalvdans left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's getting harder and harder to find things to complain on 😄

I have no merge rights, just bystander hoping that my approval will help a reviewer with write access to the repo.

SO_BINDTODEVICE,
interface.encode("ascii"),
)
except (OSError) as err:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: unnecessary parantheses

Suggested change
except (OSError) as err:
except OSError as err:

interface.encode("ascii"),
)
except (OSError) as err:
raise ICMPSocketError(f"Cannot bind to interface {interface}: {err}")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: from None will completely replace the original exception instead of making two chained exceptions.
Chained exceptions will produce tracebacks saying "while handling this, another exception occurred". Since we already include the first exception text in the second exception text, there's no need to chain them.

Suggested change
raise ICMPSocketError(f"Cannot bind to interface {interface}: {err}")
raise ICMPSocketError(f"Cannot bind to interface {interface}: {err}") from None

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants