1

I have following code, which should monitor network changes using RTNETLINK socket. However when I am setting new IP address for interface "New Addr" or "Del Addr" does not showing. What can be possible problem.

package main

import (
    "fmt"
    "syscall"
)

func main() {
    l, _ := ListenNetlink()

    for {
        msgs, err := l.ReadMsgs()
        if err != nil {
            fmt.Println("Could not read netlink: %s", err)
        }

        for _, m := range msgs {
            if IsNewAddr(&m) {
                fmt.Println("New Addr")
            }

            if IsDelAddr(&m) {
                fmt.Println("Del Addr")
            }
        }
    }
}

type NetlinkListener struct {
    fd int
    sa *syscall.SockaddrNetlink
}

func ListenNetlink() (*NetlinkListener, error) {
    groups := syscall.RTNLGRP_LINK |
        syscall.RTNLGRP_IPV4_IFADDR |
        syscall.RTNLGRP_IPV6_IFADDR

    s, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM,
        syscall.NETLINK_ROUTE)
    if err != nil {
        return nil, fmt.Errorf("socket: %s", err)
    }

    saddr := &syscall.SockaddrNetlink{
        Family: syscall.AF_NETLINK,
        Pid:    uint32(0),
        Groups: uint32(groups),
    }

    err = syscall.Bind(s, saddr)
    if err != nil {
        return nil, fmt.Errorf("bind: %s", err)
    }

    return &NetlinkListener{fd: s, sa: saddr}, nil
}

func (l *NetlinkListener) ReadMsgs() ([]syscall.NetlinkMessage, error) {
    defer func() {
        recover()
    }()

    pkt := make([]byte, 2048)

    n, err := syscall.Read(l.fd, pkt)
    if err != nil {
        return nil, fmt.Errorf("read: %s", err)
    }

    msgs, err := syscall.ParseNetlinkMessage(pkt[:n])
    if err != nil {
        return nil, fmt.Errorf("parse: %s", err)
    }

    return msgs, nil
}

func IsNewAddr(msg *syscall.NetlinkMessage) bool {
    if msg.Header.Type == syscall.RTM_NEWADDR {
        return true
    }

    return false
}

func IsDelAddr(msg *syscall.NetlinkMessage) bool {
    if msg.Header.Type == syscall.RTM_DELADDR {
        return true
    }

    return false
}

func IsRelevant(msg *syscall.IfAddrmsg) bool {
    if msg.Scope == syscall.RT_SCOPE_UNIVERSE ||
        msg.Scope == syscall.RT_SCOPE_SITE {
        return true
    }

    return false
}
Khamidulla
  • 2,927
  • 6
  • 35
  • 59

4 Answers4

2

I found bag in syscall.go file. Constant variable syscall.RTNLGRP_IPV4_IFADDR=0x5. However analog RTMGRP_IPV4_IFADDR constant in C language which is defined in rtnetlink.h source has different value as following:

#define RTMGRP_IPV4_IFADDR 0x10

I submitted issue through github.com and I hope it will fixed in upcoming releases.

For now you can use 0x10 in your code insted of 0x5. It will work perfectly.

Turns out that it is not bug at all. They did not re declare RTMGRP_* constant variables group from rtnetlink.h source and do not want to add this in feature as well since syscall.go is frozen. However they suggest using RTNLGRP_* which is also declared in rtnetlink.h source. However this two groups of constant variables is different in following way. RTMGRP_* group represents bit value (i.e.: RTMGRP_IPV4_IFADDR = 0x10) and declared for userspace backward capabilities. RTLNGRP_* group represents bit position rather than bit value (i.e.: RTNLGRP_IPV4_IFADDR=0x5) which can be translated to bit value by following way 1 << (RTNLGRP_* - 1)

Community
  • 1
  • 1
Khamidulla
  • 2,927
  • 6
  • 35
  • 59
2

As per the accepted answer, the fix it to change the groups to the following:

groups := (1 << (syscall.RTNLGRP_LINK - 1)) |
    (1 << (syscall.RTNLGRP_IPV4_IFADDR - 1)) |
    (1 << (syscall.RTNLGRP_IPV6_IFADDR - 1))
tmm1
  • 2,025
  • 1
  • 20
  • 35
1

Here's the equivalent code for *BSD:

package main

import (
    "fmt"
    "log"
    "syscall"
)

func main() {
    netlink, err := ListenNetlink()
    if err != nil {
        log.Printf("[ERR] Could not create netlink listener: %v", err)
        return
    }

    for {
        msgs, err := netlink.ReadMsgs()
        if err != nil {
            log.Printf("[ERR] Could not read netlink: %v", err)
        }

        for _, msg := range msgs {
            if _, ok := msg.(*syscall.InterfaceAddrMessage); ok {
                log.Printf("address change!")
            }
        }
    }
}

type NetlinkListener struct {
    fd int
}

func ListenNetlink() (*NetlinkListener, error) {
    s, err := syscall.Socket(syscall.AF_ROUTE, syscall.SOCK_RAW, syscall.AF_UNSPEC)
    if err != nil {
        return nil, fmt.Errorf("socket: %s", err)
    }
    return &NetlinkListener{fd: s}, nil
}

func (l *NetlinkListener) ReadMsgs() ([]syscall.RoutingMessage, error) {
    defer func() {
        recover()
    }()

    pkt := make([]byte, 2048)

    n, err := syscall.Read(l.fd, pkt)
    if err != nil {
        return nil, fmt.Errorf("read: %s", err)
    }

    msgs, err := syscall.ParseRoutingMessage(pkt[:n])
    if err != nil {
        return nil, fmt.Errorf("parse: %s", err)
    }

    return msgs, nil
}
tmm1
  • 2,025
  • 1
  • 20
  • 35
0

the update Example should be

package main

import (
    "fmt"
    "syscall"
)

func main() {
    l, _ := ListenNetlink()

    for {
        msgs, err := l.ReadMsgs()
        if err != nil {
            fmt.Println("Could not read netlink: %s", err)
        }

        for _, m := range msgs {
            if IsNewAddr(&m) {
                fmt.Println("New Addr")
            }

            if IsDelAddr(&m) {
                fmt.Println("Del Addr")
            }
        }
    }
}

type NetlinkListener struct {
    fd int
    sa *syscall.SockaddrNetlink
}

func ListenNetlink() (*NetlinkListener, error) {
    groups := (1 << (syscall.RTNLGRP_LINK - 1)) |
        (1 << (syscall.RTNLGRP_IPV4_IFADDR - 1)) |
        (1 << (syscall.RTNLGRP_IPV6_IFADDR - 1))

    s, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM,
        syscall.NETLINK_ROUTE)
    if err != nil {
        return nil, fmt.Errorf("socket: %s", err)
    }

    saddr := &syscall.SockaddrNetlink{
        Family: syscall.AF_NETLINK,
        Pid:    uint32(0),
        Groups: uint32(groups),
    }

    err = syscall.Bind(s, saddr)
    if err != nil {
        return nil, fmt.Errorf("bind: %s", err)
    }

    return &NetlinkListener{fd: s, sa: saddr}, nil
}

func (l *NetlinkListener) ReadMsgs() ([]syscall.NetlinkMessage, error) {
    defer func() {
        recover()
    }()

    pkt := make([]byte, 2048)

    n, err := syscall.Read(l.fd, pkt)
    if err != nil {
        return nil, fmt.Errorf("read: %s", err)
    }

    msgs, err := syscall.ParseNetlinkMessage(pkt[:n])
    if err != nil {
        return nil, fmt.Errorf("parse: %s", err)
    }

    return msgs, nil
}

func IsNewAddr(msg *syscall.NetlinkMessage) bool {
    if msg.Header.Type == syscall.RTM_NEWADDR {
        return true
    }

    return false
}

func IsDelAddr(msg *syscall.NetlinkMessage) bool {
    if msg.Header.Type == syscall.RTM_DELADDR {
        return true
    }

    return false
}

// rtm_scope is the distance to the destination:
//
// RT_SCOPE_UNIVERSE   global route
// RT_SCOPE_SITE       interior route in the
// local autonomous system
// RT_SCOPE_LINK       route on this link
// RT_SCOPE_HOST       route on the local host
// RT_SCOPE_NOWHERE    destination doesn't exist
//
// The values between RT_SCOPE_UNIVERSE and RT_SCOPE_SITE are
// available to the user.

func IsRelevant(msg *syscall.IfAddrmsg) bool {
    if msg.Scope == syscall.RT_SCOPE_UNIVERSE ||
        msg.Scope == syscall.RT_SCOPE_SITE {
        return true
    }

    return false
}