How to get process id by process name in windows environment? - windows

I want to get the process id by the process name in windows environment?
I find golang only has the api os.FindProcess(id),but no by name.

I had to struggle with this too, and found the way to the solution not very straightforward, becauseā€¦ WinApi :)
In the end you have to create a snapshot of the current windows process list using CreateToolhelp32Snapshot. Then you get the first process in the snapshot with Process32First. After that keep iterating over the list with Process32Next, until you get the ERROR_NO_MORE_FILES error. Only then you have the whole process list.
See how2readwindowsprocesses for a working example.
Here is the gist:
const TH32CS_SNAPPROCESS = 0x00000002
type WindowsProcess struct {
ProcessID int
ParentProcessID int
Exe string
}
func processes() ([]WindowsProcess, error) {
handle, err := windows.CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
if err != nil {
return nil, err
}
defer windows.CloseHandle(handle)
var entry windows.ProcessEntry32
entry.Size = uint32(unsafe.Sizeof(entry))
// get the first process
err = windows.Process32First(handle, &entry)
if err != nil {
return nil, err
}
results := make([]WindowsProcess, 0, 50)
for {
results = append(results, newWindowsProcess(&entry))
err = windows.Process32Next(handle, &entry)
if err != nil {
// windows sends ERROR_NO_MORE_FILES on last process
if err == syscall.ERROR_NO_MORE_FILES {
return results, nil
}
return nil, err
}
}
}
func findProcessByName(processes []WindowsProcess, name string) *WindowsProcess {
for _, p := range processes {
if strings.ToLower(p.Exe) == strings.ToLower(name) {
return &p
}
}
return nil
}
func newWindowsProcess(e *windows.ProcessEntry32) WindowsProcess {
// Find when the string ends for decoding
end := 0
for {
if e.ExeFile[end] == 0 {
break
}
end++
}
return WindowsProcess{
ProcessID: int(e.ProcessID),
ParentProcessID: int(e.ParentProcessID),
Exe: syscall.UTF16ToString(e.ExeFile[:end]),
}
}

You can list all the processes and match them with the name you want to find, by using the updated sys call package, https://godoc.org/golang.org/x/sys,
it has most of the windows api.
func Process32First(snapshot Handle, procEntry *ProcessEntry32) (err error)
func Process32Next(snapshot Handle, procEntry *ProcessEntry32) (err error)
also see the msdn docs:
https://msdn.microsoft.com/en-us/library/windows/desktop/ms684834(v=vs.85).aspx

const TH32CS_SNAPPROCESS = 0x00000002
type WindowsProcess struct {
ProcessID int
ParentProcessID int
Exe string
}
func newWindowsProcess(e *syscall.ProcessEntry32) WindowsProcess {
// Find when the string ends for decoding
end := 0
for {
if e.ExeFile[end] == 0 {
break
}
end++
}
return WindowsProcess{
ProcessID: int(e.ProcessID),
ParentProcessID: int(e.ParentProcessID),
Exe: syscall.UTF16ToString(e.ExeFile[:end]),
}
}
func processes() ([]WindowsProcess, error) {
handle, err := syscall.CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
if err != nil {
return nil, err
}
defer syscall.CloseHandle(handle)
var entry syscall.ProcessEntry32
entry.Size = uint32(unsafe.Sizeof(entry))
// get the first process
err = syscall.Process32First(handle, &entry)
if err != nil {
return nil, err
}
results := make([]WindowsProcess, 0, 50)
for {
results = append(results, newWindowsProcess(&entry))
err = syscall.Process32Next(handle, &entry)
if err != nil {
// windows sends ERROR_NO_MORE_FILES on last process
if err == syscall.ERROR_NO_MORE_FILES {
return results, nil
}
return nil, err
}
}
}
func findProcessByName(processes []WindowsProcess, name string) *WindowsProcess {
for _, p := range processes {
if bytes.Contains([]byte(strings.ToUpper(p.Exe)), []byte(strings.ToUpper(name))) {
return &p
}
}
return nil
}

This seems to do it:
package main
import (
"fmt"
"golang.org/x/sys/windows"
)
// unsafe.Sizeof(windows.ProcessEntry32{})
const processEntrySize = 568
func processID(name string) (uint32, error) {
h, e := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0)
if e != nil { return 0, e }
p := windows.ProcessEntry32{Size: processEntrySize}
for {
e := windows.Process32Next(h, &p)
if e != nil { return 0, e }
if windows.UTF16ToString(p.ExeFile[:]) == name {
return p.ProcessID, nil
}
}
return 0, fmt.Errorf("%q not found", name)
}
func main() {
n, e := processID("WindowsTerminal.exe")
if e != nil {
panic(e)
}
println(n)
}
https://pkg.go.dev/golang.org/x/sys/windows#CreateToolhelp32Snapshot

Related

Elegant way to eliminate multiple condition checks in function

There are multiple condition checks in multiple functions
type VA struct {
A string
}
func (va *VA) CheckA(s string) error {
if s != va.A {
return errors.New("invalid str ")
}
return nil
}
type VB struct {
B int
}
func (vb *VB) CheckB(i int) error {
if i == vb.B {
return errors.New("invalid int")
}
return nil
}
func FuncA(s string, i int) error {
a := &VA{A: "testa"}
errA := a.CheckA(s)
if errA != nil {
return errA
}
b := &VB{B: 3}
errB := b.CheckB(i)
if errB != nil {
return errB
}
// more logic ...
return nil
}
func FuncB(sb string, v int32) error {
a := &VA{A: "testb"}
errA := a.CheckA(sb)
if errA != nil {
return errA
}
// more logic ...
return nil
}
func FuncC(sc string, vv int) error {
b := &VB{B: 3}
errB := b.CheckB(vv)
if errB != nil {
return errB
}
// more logic ...
return nil
}
We do CheckA and CheckB in function FuncA and do CheckA in function FuncB. However, only do CheckB in function FuncC. There is one pitfall that when the return value of CheckA is changed, both FuncA and FuncB would be changed.
We want to refactor the above codes. Is there any elegant way to do that in Golang?
What we have tried, combine CheckA and CheckB in one function ValidateFunc like below
type VALIDATE int
const (
_ VALIDATE = 1 << iota
VALIDATEA
VALIDATEB
)
func ValidateFunc(vs, s string, vi, i int, validate VALIDATE) error {
if validate&VALIDATEA == VALIDATEA {
a := &VA{A: vs}
errA := a.CheckA(s)
if errA != nil {
return errA
}
}
if validate&VALIDATEB == VALIDATEB {
b := &VB{B: vi}
errB := b.CheckB(i)
if errB != nil {
return errB
}
}
return nil
}
func FuncA(s string, i int) error {
err := ValidateFunc("testa", s, 3, i, VALIDATEA|VALIDATEB)
if err != nil {
return err
}
// more logic ...
return nil
}
Refer to Option pattern, it seems the codes will be simpler than before.
type Validator struct {
A VA
B VB
}
type Validate func(v *Validator) error
func WithVA(s string) Validate {
return func(v *Validator) error {
if err := v.A.CheckA(s); err != nil {
return err
}
return nil
}
}
func WithVB(i int) Validate {
return func(v *Validator) error {
if err := v.B.CheckB(i); err != nil {
return err
}
return nil
}
}
func DoValidate(vs string, vi int, vals ...func(v *Validator) error) error {
v := &Validator{A: VA{A: vs}, B: VB{B: vi}}
for _, val := range vals {
if err := val(v); err != nil {
return err
}
}
return nil
}
func FuncA(s string, i int) error {
err := DoValidate("testa", 3, WithVA(s), WithVB(i))
if err != nil {
return err
}
// more logic ...
return nil
}
Perhaps combine VA & VB into a new struct
Then validate that one?

How to extend go-yaml to support custom tags

I have spent some time reading the code and docs of go-yaml, but I have not found any way to do this, except forking the project..
I want to extend the YAML unmarshaller so that it can accept a custom YAML tag (!include <file> in this case), which in turn would allow me to add support for including files. This is easily implemented with other YAML libraries, like in this answer.
Is there any way to accomplish this, using the public interface of the library (or another yaml library)?
Yes, this is possible (since v3). You can load the whole YAML file into a yaml.Node and then walk over the structure. The trick is that yaml.Node is an intermediate representation which you can only access if you define an unmarshaler.
For example:
package main
import (
"errors"
"fmt"
"io/ioutil"
"gopkg.in/yaml.v3"
)
// used for loading included files
type Fragment struct {
content *yaml.Node
}
func (f *Fragment) UnmarshalYAML(value *yaml.Node) error {
var err error
// process includes in fragments
f.content, err = resolveIncludes(value)
return err
}
type IncludeProcessor struct {
target interface{}
}
func (i *IncludeProcessor) UnmarshalYAML(value *yaml.Node) error {
resolved, err := resolveIncludes(value)
if err != nil {
return err
}
return resolved.Decode(i.target)
}
func resolveIncludes(node *yaml.Node) (*yaml.Node, error) {
if node.Tag == "!include" {
if node.Kind != yaml.ScalarNode {
return nil, errors.New("!include on a non-scalar node")
}
file, err := ioutil.ReadFile(node.Value)
if err != nil {
return nil, err
}
var f Fragment
err = yaml.Unmarshal(file, &f)
return f.content, err
}
if node.Kind == yaml.SequenceNode || node.Kind == yaml.MappingNode {
var err error
for i := range node.Content {
node.Content[i], err = resolveIncludes(node.Content[i])
if err != nil {
return nil, err
}
}
}
return node, nil
}
type MyStructure struct {
// this structure holds the values you want to load after processing
// includes, e.g.
Num int
}
func main() {
var s MyStructure
yaml.Unmarshal([]byte("!include foo.yaml"), &IncludeProcessor{&s})
fmt.Printf("Num: %v", s.Num)
}
Code prints Num: 42 when a file foo.yaml exists with the content num: 42.
Modified #flyx's original code a little to make it modular for adding custom resolvers.
package main
import (
"errors"
"fmt"
"io/ioutil"
"os"
"gopkg.in/yaml.v3"
)
var tagResolvers = make(map[string]func(*yaml.Node) (*yaml.Node, error))
type Fragment struct {
content *yaml.Node
}
func (f *Fragment) UnmarshalYAML(value *yaml.Node) error {
var err error
// process includes in fragments
f.content, err = resolveTags(value)
return err
}
type CustomTagProcessor struct {
target interface{}
}
func (i *CustomTagProcessor) UnmarshalYAML(value *yaml.Node) error {
resolved, err := resolveTags(value)
if err != nil {
return err
}
return resolved.Decode(i.target)
}
func resolveTags(node *yaml.Node) (*yaml.Node, error) {
for tag, fn := range tagResolvers {
if node.Tag == tag {
return fn(node)
}
}
if node.Kind == yaml.SequenceNode || node.Kind == yaml.MappingNode {
var err error
for i := range node.Content {
node.Content[i], err = resolveTags(node.Content[i])
if err != nil {
return nil, err
}
}
}
return node, nil
}
func resolveIncludes(node *yaml.Node) (*yaml.Node, error) {
if node.Kind != yaml.ScalarNode {
return nil, errors.New("!include on a non-scalar node")
}
file, err := ioutil.ReadFile(node.Value)
if err != nil {
return nil, err
}
var f Fragment
err = yaml.Unmarshal(file, &f)
return f.content, err
}
func resolveGetValueFromEnv(node *yaml.Node) (*yaml.Node, error) {
if node.Kind != yaml.ScalarNode {
return nil, errors.New("!getValueFromEnv on a non-scalar node")
}
value := os.Getenv(node.Value)
if value == "" {
return nil, fmt.Errorf("environment variable %v not set", node.Value)
}
var f Fragment
err := yaml.Unmarshal([]byte(value), &f)
return f.content, err
}
func AddResolvers(tag string, fn func(*yaml.Node) (*yaml.Node, error)) {
tagResolvers[tag] = fn
}
func main() {
// Register custom tag resolvers
AddResolvers("!include", resolveIncludes)
AddResolvers("!getValueFromEnv", resolveGetValueFromEnv)
type MyStructure struct {
// this structure holds the values you want to load after processing
// includes, e.g.
Num int
}
var s MyStructure
os.Setenv("FOO", `{"num": 42}`)
err := yaml.Unmarshal([]byte("!getValueFromEnv FOO"), &CustomTagProcessor{&s})
if err != nil {
panic("Error encountered during unmarshalling")
}
fmt.Printf("\nNum: %v", s.Num)
err = yaml.Unmarshal([]byte("!include foo.yaml"), &CustomTagProcessor{&s})
if err != nil {
panic("Error encountered during unmarshalling")
}
fmt.Printf("\nNum: %v", s.Num)
}

Golang - Set syscall user context in Windows

I am making two syscalls to the IIS Hosted Web Core (HWC) WebCoreActivate and WebCoreShutdown APIs. I would like to add a user context, so when the syscalls are made in Windows, they are run underneath a specific user's context - i.e., that user's permissions.
type WebCore struct {
activated bool
Handle syscall.Handle
Credentials syscall.Credential
}
func New() (error, *WebCore) {
hwebcore, err := syscall.LoadLibrary(os.ExpandEnv(`${windir}\system32\inetsrv\hwebcore.dll`))
if err != nil {
return err, nil
}
return nil, &WebCore{
activated: false,
Handle: hwebcore,
}
}
func (w *WebCore) Activate(appHostConfigPath, rootWebConfigPath, instanceName string) error {
if !w.activated {
webCoreActivate, err := syscall.GetProcAddress(w.Handle, "WebCoreActivate")
if err != nil {
return err
}
var nargs uintptr = 3
_, _, exitCode := syscall.Syscall(uintptr(webCoreActivate),
nargs,
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(appHostConfigPath))),
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(rootWebConfigPath))),
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(instanceName))))
if exitCode != 0 {
return fmt.Errorf("WebCoreActivate returned exit code: %d", exitCode)
}
fmt.Printf("Server Started for %+v\n", instanceName)
w.activated = true
}
return nil
}
func (w *WebCore) Shutdown(immediate int, instanceName string) error {
if w.activated {
webCoreShutdown, err := syscall.GetProcAddress(w.Handle, "WebCoreShutdown")
if err != nil {
return err
}
var nargs uintptr = 1
_, _, exitCode := syscall.Syscall(uintptr(webCoreShutdown),
nargs, uintptr(unsafe.Pointer(&immediate)), 0, 0)
if exitCode != 0 {
return fmt.Errorf("WebCoreShutdown returned exit code: %d", exitCode)
}
fmt.Printf("Server Shutdown for %+v\n", instanceName)
}
return nil
}
I've been reading about the exec.Command().SysProcAttr structure, and how you can attach specific user credentials via the syscall.Credentials{} structure. However, that seems limited to executing commands via os.exec. If I wanted to tie a specific user's context to a native syscall in Windows, how would I do that?

Change the sample by using goroutine?

I found a good web invalid links checker. But how to change it for a complete sample by using goroutine? The web page is: How To Crawl A Website In Golang. The codes dynamically add the url that will be searched to the pending slice. but I have some difficulties to use goroutine to do it.
package main
import (
"crypto/tls"
"errors"
"fmt"
"golang.org/x/net/html"
"io"
"net/http"
"net/url"
"strings"
"time"
)
var alreadyCrawledList []string
var pending []string
var brokenLinks []string
const localHostWithPort = "localhost:8080"
func IsLinkInPendingQueue(link string) bool {
for _, x := range pending {
if x == link {
return true
}
}
return false
}
func IsLinkAlreadyCrawled(link string) bool {
for _, x := range alreadyCrawledList {
if x == link {
return true
}
}
return false
}
func AddLinkInAlreadyCrawledList(link string) {
alreadyCrawledList = append(alreadyCrawledList, link)
}
func AddLinkInPendingQueue(link string) {
pending = append(pending, link)
}
func AddLinkInBrokenLinksQueue(link string) {
brokenLinks = append(brokenLinks, link)
}
func main() {
start := time.Now()
AddLinkInPendingQueue("http://" + localHostWithPort)
for count := 0; len(pending) > 0; count++ {
x := pending[0]
pending = pending[1:] //it dynamicly change the search url
if err := crawlPage(x); err != nil { //how to use it by using goroutine?
t.Errorf(err.Error())
}
}
duration := time.Since(start)
fmt.Println("________________")
count = 0
for _, l := range brokenLinks {
count++
fmt.Println(count, "Broken. | ", l)
}
fmt.Println("Time taken:", duration)
}
func crawlPage(uri string) error {
if IsLinkAlreadyCrawled(uri) {
fmt.Println("Already visited: Ignoring uri | ", uri)
return nil
}
transport := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
}
client := http.Client{Transport: transport}
resp, err := client.Get(uri)
if err != nil {
fmt.Println("Got error: ", err.Error())
return err
}
if resp.StatusCode != http.StatusOK {
AddLinkInBrokenLinksQueue(uri)
return errors.New(fmt.Sprintf("Got %v instead of 200", resp.StatusCode))
}
defer resp.Body.Close()
links := ParseLinks(resp.Body)
links = ConvertLinksToLocalHost(links)
for _, link := range links {
if !InOurDomain(link) {
continue
}
absolute := FixURL(link, uri)
if !IsLinkAlreadyCrawled(absolute) && !IsLinkInPendingQueue(absolute) && absolute != uri { // Don't enqueue a page twice!
AddLinkInPendingQueue(absolute)
}
}
AddLinkInAlreadyCrawledList(uri)
return nil
}
func InOurDomain(link string) bool {
uri, err := url.Parse(link)
if err != nil {
return false
}
if uri.Scheme == "http" || uri.Scheme == "https" {
if uri.Host == localHostWithPort {
return true
}
return false
}
return true
}
func ConvertLinksToLocalHost(links []string) []string {
var convertedLinks []string
for _, link := range links {
convertedLinks = append(convertedLinks, strings.Replace(link, "leantricks.com", localHostWithPort, 1))
}
return convertedLinks
}
func FixURL(href, base string) string {
uri, err := url.Parse(href)
if err != nil {
return ""
}
baseURL, err := url.Parse(base)
if err != nil {
return ""
}
uri = baseURL.ResolveReference(uri)
return uri.String()
}
func ParseLinks(httpBody io.Reader) []string {
var links []string
page := html.NewTokenizer(httpBody)
for {
tokenType := page.Next()
if tokenType == html.ErrorToken {
return links
}
token := page.Token()
switch tokenType {
case html.StartTagToken:
fallthrough
case html.SelfClosingTagToken:
switch token.DataAtom.String() {
case "a":
fallthrough
case "link":
fallthrough
case "script":
for _, attr := range token.Attr {
if attr.Key == "href" {
links = append(links, attr.Val)
}
}
}
}
}
}
You could invoke the crawlPage() concurrently and handle alreadyCrawledList, pending and brokenLinks variables with mutexes (not so performant though). On the other hand, the code needs to be modified a lot to get it more performant.
I did a quick check with 4 links and seems to half the duration. I did a sample code with a simple http server and its here
Thanks,
- Anoop

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

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
}
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)
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))
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
}
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
}

Resources