mirror of
https://github.com/fosrl/newt.git
synced 2026-08-31 03:01:28 +02:00
add funcs to add and remove ip from interface
This commit is contained in:
@@ -167,3 +167,95 @@ func configureLinux(interfaceName string, ip net.IP, ipNet *net.IPNet) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddSecondaryAddress adds an additional IP address (given as CIDR, e.g. "10.10.0.5/32")
|
||||
// to an already-configured interface. It also records the address in the shared
|
||||
// NetworkSettings (see AddIPv4Address) so mobile (iOS/Android) packet-tunnel providers
|
||||
// pick it up on their next settings poll - those platforms have no OS-level interface
|
||||
// to configure directly, so this is the only way they learn about the address.
|
||||
func AddSecondaryAddress(interfaceName string, addr string) error {
|
||||
ip, ipNet, err := net.ParseCIDR(addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid IP address: %v", err)
|
||||
}
|
||||
|
||||
mask := net.IP(ipNet.Mask).String()
|
||||
AddIPv4Address(ip.String(), mask)
|
||||
|
||||
if interfaceName == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
return configureLinux(interfaceName, ip, ipNet)
|
||||
case "darwin":
|
||||
return configureDarwin(interfaceName, ip, ipNet)
|
||||
case "windows":
|
||||
return configureWindows(interfaceName, ip, ipNet)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveSecondaryAddress removes an IP address (given as CIDR) previously added with
|
||||
// AddSecondaryAddress, including from the shared NetworkSettings used by mobile
|
||||
// packet-tunnel providers.
|
||||
func RemoveSecondaryAddress(interfaceName string, addr string) error {
|
||||
ip, ipNet, err := net.ParseCIDR(addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid IP address: %v", err)
|
||||
}
|
||||
|
||||
RemoveIPv4Address(ip.String())
|
||||
|
||||
if interfaceName == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
return removeLinuxAddress(interfaceName, ip, ipNet)
|
||||
case "darwin":
|
||||
return removeDarwinAddress(interfaceName, ip, ipNet)
|
||||
case "windows":
|
||||
return removeWindowsAddress(interfaceName, ip, ipNet)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func removeLinuxAddress(interfaceName string, ip net.IP, ipNet *net.IPNet) error {
|
||||
link, err := netlink.LinkByName(interfaceName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get interface %s: %v", interfaceName, err)
|
||||
}
|
||||
|
||||
addr := &netlink.Addr{
|
||||
IPNet: &net.IPNet{
|
||||
IP: ip,
|
||||
Mask: ipNet.Mask,
|
||||
},
|
||||
}
|
||||
|
||||
if err := netlink.AddrDel(link, addr); err != nil {
|
||||
return fmt.Errorf("failed to remove IP address: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeDarwinAddress(interfaceName string, ip net.IP, ipNet *net.IPNet) error {
|
||||
prefix, _ := ipNet.Mask.Size()
|
||||
ipStr := fmt.Sprintf("%s/%d", ip.String(), prefix)
|
||||
|
||||
cmd := exec.Command("/sbin/ifconfig", interfaceName, "inet", ipStr, "-alias")
|
||||
logger.Info("Running command: %v", cmd)
|
||||
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("ifconfig command failed: %v, output: %s", err, out)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -10,3 +10,7 @@ import (
|
||||
func configureWindows(interfaceName string, ip net.IP, ipNet *net.IPNet) error {
|
||||
return fmt.Errorf("configureWindows called on non-Windows platform")
|
||||
}
|
||||
|
||||
func removeWindowsAddress(interfaceName string, ip net.IP, ipNet *net.IPNet) error {
|
||||
return fmt.Errorf("removeWindowsAddress called on non-Windows platform")
|
||||
}
|
||||
|
||||
@@ -61,3 +61,35 @@ func configureWindows(interfaceName string, ip net.IP, ipNet *net.IPNet) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeWindowsAddress(interfaceName string, ip net.IP, ipNet *net.IPNet) error {
|
||||
iface, err := net.InterfaceByName(interfaceName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get interface %s: %v", interfaceName, err)
|
||||
}
|
||||
|
||||
luid, err := winipcfg.LUIDFromIndex(uint32(iface.Index))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get LUID for interface %s: %v", interfaceName, err)
|
||||
}
|
||||
|
||||
maskBits, _ := ipNet.Mask.Size()
|
||||
|
||||
var addr netip.Addr
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
addr, _ = netip.AddrFromSlice(ip4)
|
||||
} else {
|
||||
addr, _ = netip.AddrFromSlice(ip)
|
||||
}
|
||||
if !addr.IsValid() {
|
||||
return fmt.Errorf("failed to convert IP address")
|
||||
}
|
||||
prefix := netip.PrefixFrom(addr, maskBits)
|
||||
|
||||
logger.Info("Removing IP address %s from interface %s", prefix.String(), interfaceName)
|
||||
if err := luid.DeleteIPAddress(prefix); err != nil {
|
||||
return fmt.Errorf("failed to remove IP address: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -81,6 +81,45 @@ func SetIPv4Settings(addresses []string, subnetMasks []string) {
|
||||
logger.Info("Set IPv4 addresses: %v, subnet masks: %v", addresses, subnetMasks)
|
||||
}
|
||||
|
||||
// AddIPv4Address appends an additional IPv4 address/subnet mask pair to the
|
||||
// tunnel's network settings. This is how a secondary interface address gets
|
||||
// exposed to mobile (iOS/Android) packet-tunnel providers, which read the
|
||||
// full IPv4Addresses/IPv4SubnetMasks arrays (not just the first entry) and
|
||||
// re-apply them on every settings poll.
|
||||
func AddIPv4Address(address string, subnetMask string) {
|
||||
networkSettingsMutex.Lock()
|
||||
defer networkSettingsMutex.Unlock()
|
||||
|
||||
for _, a := range networkSettings.IPv4Addresses {
|
||||
if a == address {
|
||||
logger.Info("IPv4 address already exists: %s", address)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
networkSettings.IPv4Addresses = append(networkSettings.IPv4Addresses, address)
|
||||
networkSettings.IPv4SubnetMasks = append(networkSettings.IPv4SubnetMasks, subnetMask)
|
||||
incrementor++
|
||||
logger.Info("Added IPv4 address: %s/%s", address, subnetMask)
|
||||
}
|
||||
|
||||
// RemoveIPv4Address removes a previously added secondary IPv4 address.
|
||||
func RemoveIPv4Address(address string) {
|
||||
networkSettingsMutex.Lock()
|
||||
defer networkSettingsMutex.Unlock()
|
||||
|
||||
for i, a := range networkSettings.IPv4Addresses {
|
||||
if a == address {
|
||||
networkSettings.IPv4Addresses = append(networkSettings.IPv4Addresses[:i], networkSettings.IPv4Addresses[i+1:]...)
|
||||
networkSettings.IPv4SubnetMasks = append(networkSettings.IPv4SubnetMasks[:i], networkSettings.IPv4SubnetMasks[i+1:]...)
|
||||
incrementor++
|
||||
logger.Info("Removed IPv4 address: %s", address)
|
||||
return
|
||||
}
|
||||
}
|
||||
logger.Info("IPv4 address not found for removal: %s", address)
|
||||
}
|
||||
|
||||
// SetIPv4IncludedRoutes sets the included IPv4 routes
|
||||
func SetIPv4IncludedRoutes(routes []IPv4Route) {
|
||||
networkSettingsMutex.Lock()
|
||||
|
||||
@@ -28,6 +28,15 @@ import (
|
||||
"go.opentelemetry.io/otel"
|
||||
)
|
||||
|
||||
// writeDeadline bounds how long a websocket write may block before it is
|
||||
// treated as a failure. Without this, a write to a TCP connection whose
|
||||
// underlying network interface has disappeared (e.g. laptop sleep/resume,
|
||||
// Wi-Fi roam) can sit buffered in the kernel for minutes without erroring.
|
||||
// This matters even with the read-deadline/pong machinery below: if the
|
||||
// WriteJSON call in sendPing blocks, execution never reaches the
|
||||
// WriteControl ping that would otherwise trigger that read-side detection.
|
||||
const writeDeadline = 10 * time.Second
|
||||
|
||||
type Client struct {
|
||||
conn *websocket.Conn
|
||||
config *Config
|
||||
@@ -257,6 +266,9 @@ func (c *Client) SendMessage(messageType string, data interface{}) error {
|
||||
|
||||
c.writeMux.Lock()
|
||||
defer c.writeMux.Unlock()
|
||||
if err := c.conn.SetWriteDeadline(time.Now().Add(writeDeadline)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.conn.WriteJSON(msg); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -277,6 +289,9 @@ func (c *Client) SendMessageNoLog(messageType string, data interface{}) error {
|
||||
|
||||
c.writeMux.Lock()
|
||||
defer c.writeMux.Unlock()
|
||||
if err := c.conn.SetWriteDeadline(time.Now().Add(writeDeadline)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.conn.WriteJSON(msg); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -760,14 +775,17 @@ func (c *Client) sendPing() {
|
||||
c.writeMux.Unlock()
|
||||
return
|
||||
}
|
||||
err := c.conn.WriteJSON(pingMsg)
|
||||
err := c.conn.SetWriteDeadline(time.Now().Add(writeDeadline))
|
||||
if err == nil {
|
||||
err = c.conn.WriteJSON(pingMsg)
|
||||
}
|
||||
if err == nil {
|
||||
telemetry.IncWSMessage(c.metricsContext(), "out", "ping")
|
||||
// Protocol-level ping: a standards-compliant server replies with a PONG,
|
||||
// which refreshes the read deadline. This is what lets us notice a
|
||||
// half-open connection where writes still succeed (buffered) but the
|
||||
// peer is gone.
|
||||
_ = c.conn.WriteControl(websocket.PingMessage, nil, time.Now().Add(10*time.Second))
|
||||
_ = c.conn.WriteControl(websocket.PingMessage, nil, time.Now().Add(writeDeadline))
|
||||
}
|
||||
c.writeMux.Unlock()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user