How to monitor ip address change using RTNETLINK socket in go language

旧巷老猫 提交于 2019-12-23 02:42:40

问题


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
}

回答1:


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)




回答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))



回答3:


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
}


来源:https://stackoverflow.com/questions/36347807/how-to-monitor-ip-address-change-using-rtnetlink-socket-in-go-language

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!