Golang Gorilla Websocket stops receiving information at 120 seconds - go

I'm currently trying to connect to the CEX.IO bitcoin exchange's websocket, but have been having issues not only with CEX.IO but with others too. All of my connections drop around the 120-second mark which makes me think there is some TTL problem going on. The Process() goroutine in the main package ends up just hanging and waiting for data from the readLoop which just stops receiving data. I've included some read-only API keys in the code so you can test if you'd like.
package main
import (
"fmt"
"bitbucket.org/tradedefender/cryptocurrency/exchange-connector/cexio"
"github.com/shopspring/decimal"
"encoding/json"
"time"
)
type OrderBook struct {
Asks []Ask
Bids []Bid
}
type Ask struct {
Rate decimal.Decimal
Amount decimal.Decimal
}
type Bid struct {
Rate decimal.Decimal
Amount decimal.Decimal
}
func main() {
cexioConn := new(cexio.Connection)
err := cexioConn.Connect()
if err != nil {
fmt.Errorf("error: %s", err.Error())
}
err = cexioConn.Authenticate("TLwYkktLf7Im6nqSKt6UO1IrU", "9ImOJcR7Qj3LMIyPCzky0D7WE")
if err != nil {
fmt.Errorf("error: %s", err.Error())
}
readChannel := make(chan cexio.IntraAppMessage, 25)
go cexioConn.ReadLoop(readChannel)
processor := Processor{
WatchPairs: [][2]string{
[2]string{
"BTC", "USD",
},
},
conn: cexioConn,
}
go processor.Process(readChannel)
// LOL
for {
continue
}
}
type Processor struct {
WatchPairs [][2]string
conn *cexio.Connection
}
func (p *Processor) Process(ch <-chan cexio.IntraAppMessage) {
p.conn.SubscribeToOrderBook(p.WatchPairs[0])
pingTimer := time.Now().Unix()
for {
fmt.Printf("(%v)\n", time.Now().Unix())
if (time.Now().Unix() - pingTimer) >= 10 {
fmt.Println("sending ping")
p.conn.SendPing()
pingTimer = time.Now().Unix()
}
readMsg := <- ch
output, _ := json.Marshal(readMsg.SocketMessage)
fmt.Println(string(output))
if readMsg.SocketMessage.Event == "ping" {
fmt.Println("sending pong")
p.conn.SendPong()
pingTimer = time.Now().Unix()
}
}
}
Below is the connector to the cexio websocket. Here is a link to their API: https://cex.io/websocket-api
package cexio
import (
"github.com/gorilla/websocket"
//"github.com/shopspring/decimal"
"github.com/satori/go.uuid"
"encoding/hex"
"encoding/json"
"crypto/hmac"
"crypto/sha256"
"bytes"
"strconv"
"time"
"fmt"
)
const Url = "wss://ws.cex.io/ws/"
type Connection struct {
conn *websocket.Conn
}
type IntraAppMessage struct {
SocketMessage GenericMessage
ProgramMessage ProgramMessage
}
type GenericMessage struct {
Event string `json:"e"`
Data interface{} `json:"data"`
Auth AuthData `json:"auth,omitempty"`
Ok string `json:"ok,omitempty"`
Oid string `json:"oid,omitempty"`
Time int64 `json:"time,omitempty"`
}
type ProgramMessage struct {
Error string
}
type AuthData struct {
Key string `json:"key"`
Signature string `json:"signature"`
Timestamp int64 `json:"timestamp"`
}
type OrderBookSubscribeData struct {
Pair [2]string `json:"pair"`
Subscribe bool `json:"subscribe"`
Depth int `json:"depth"`
}
func (c *Connection) SendPong() error {
pongMsg := GenericMessage{
Event: "pong",
}
err := c.conn.WriteJSON(pongMsg)
if err != nil {
return nil
}
deadline := time.Now().Add(15*time.Second)
err = c.conn.WriteControl(websocket.PongMessage, nil, deadline)
if err != nil {
return err
}
return nil
}
func (c *Connection) SendPing() error {
pingMsg := GenericMessage{
Event: "get-balance",
Oid: uuid.NewV4().String(),
}
err := c.conn.WriteJSON(pingMsg)
if err != nil {
return err
}
deadline := time.Now().Add(15*time.Second)
err = c.conn.WriteControl(websocket.PingMessage, nil, deadline)
if err != nil {
return err
}
return nil
}
func (c *Connection) Connect() error {
dialer := *websocket.DefaultDialer
wsConn, _, err := dialer.Dial(Url, nil)
if err != nil {
return err
}
c.conn = wsConn
//c.conn.SetPingHandler(c.HandlePing)
for {
_, msgBytes, err := c.conn.ReadMessage()
if err != nil {
c.Disconnect()
return err
}
fmt.Println(string(msgBytes))
var m GenericMessage
err = json.Unmarshal(msgBytes, &m)
if err != nil {
c.Disconnect()
return err
}
if m.Event != "connected" {
c.Disconnect()
return err
} else {
break
}
}
return nil
}
func (c *Connection) Disconnect() error {
return c.conn.Close()
}
func (c *Connection) ReadLoop(ch chan<- IntraAppMessage) {
for {
fmt.Println("starting new read")
_, msgBytes, err := c.conn.ReadMessage()
if err != nil {
ch <- IntraAppMessage{
ProgramMessage: ProgramMessage{
Error: err.Error(),
},
}
continue
}
var m GenericMessage
err = json.Unmarshal(msgBytes, &m)
if err != nil {
ch <- IntraAppMessage{
ProgramMessage: ProgramMessage{
Error: err.Error(),
},
}
continue
}
ch <- IntraAppMessage{
SocketMessage: m,
}
}
}
func CreateSignature(timestamp int64, key, secret string) string {
secretBytes := []byte(secret)
h := hmac.New(sha256.New, secretBytes)
var buffer bytes.Buffer
buffer.WriteString(strconv.FormatInt(timestamp, 10))
buffer.WriteString(key)
h.Write(buffer.Bytes())
return hex.EncodeToString(h.Sum(nil))
}
func (c *Connection) Authenticate(key, secret string) error {
timestamp := time.Now().Unix()
signature := CreateSignature(timestamp, key, secret)
var authMsg GenericMessage
authMsg.Event = "auth"
authMsg.Auth = AuthData{
Key: key,
Signature: signature,
Timestamp: timestamp,
}
err := c.conn.WriteJSON(authMsg)
if err != nil {
return err
}
for {
_, msgBytes, err := c.conn.ReadMessage()
if err != nil {
c.Disconnect()
return err
}
fmt.Println(string(msgBytes))
var m GenericMessage
err = json.Unmarshal(msgBytes, &m)
if err != nil {
c.Disconnect()
return err
}
if m.Event != "auth" && m.Ok != "ok" {
c.Disconnect()
return err
} else {
break
}
}
return nil
}
func (c *Connection) SubscribeToOrderBook(pair [2]string) error {
sendMsg := GenericMessage{
Event: "order-book-subscribe",
Data: OrderBookSubscribeData{
Pair: pair,
Subscribe: true,
Depth: 0,
},
Oid: uuid.NewV4().String(),
}
err := c.conn.WriteJSON(sendMsg)
if err != nil {
return err
}
return nil
}
func (c *Connection) GetBalance() error {
sendMsg := GenericMessage{
Event: "get-balance",
Oid: uuid.NewV4().String(),
}
err := c.conn.WriteJSON(sendMsg)
if err != nil {
return err
}
return nil
}

Solution was to remove the
for {
continue
}
at the end of the main function

Related

I get an error in golang test and would like to know how to improve it

sta9_test.go:124: Cannot convert id to numeric, got = id
sta9_test.go:124: Cannot convert id to numericCannot convert id to numeric, got = id
sta9_test.go:145: It is a value we do not expect.
map[string]any{
- "UpdatedAt": string("2022-10-27T15:19:10Z"),
- "createdAenter code heret": string("2022-10-27T15:19:10Z"),
"description": string(""),}
func TestStation9(t *testing.T) {
dbPath := "./temp_test.db"
if err := os.Setenv("DB_PATH", dbPath); err != nil {
t.Error("dbPathのセットに失敗しました。", err)
return
}
t.Cleanup(func() {
if err := os.Remove(dbPath); err != nil {
t.Errorf("テスト用のDBファイルの削除に失敗しました: %v", err)
return
}
})
todoDB, err := db.NewDB(dbPath)
if err != nil {
t.Error("DBの作成に失敗しました。", err)
return
}
defer func(todoDB *sql.DB) {
err := todoDB.Close()
if err != nil {
t.Error("DBのクローズに失敗しました.", err)
}
}(todoDB)
r := router.NewRouter(todoDB)
srv := httptest.NewServer(r)
defer srv.Close()
testcases := map[string]struct {
Subject string
Description string
WantHTTPStatusCode int
}{
"Subject is empty": {
WantHTTPStatusCode: http.StatusBadRequest,
},
"Description is empty": {
Subject: "todo subject",
WantHTTPStatusCode: http.StatusOK,
},
"Subject and Description is not empty": {
Subject: "todo subject",
Description: "todo description",
WantHTTPStatusCode: http.StatusOK,
},
}
for name, tc := range testcases {
name := name
tc := tc
t.Run(name, func(t *testing.T) {
resp, err := http.Post(srv.URL+"/todos", "application/json",
bytes.NewBufferString(fmt.Sprintf(`{"subject":"%s","description":"%s"}`, tc.Subject, tc.Description)))
if err != nil {
t.Error("リクエストの送信に失敗しました。", err)
return
}
defer func() {
if err := resp.Body.Close(); err != nil {
t.Error("レスポンスのクローズに失敗しました。", err)
return
}
}()
if resp.StatusCode != tc.WantHTTPStatusCode {
t.Errorf("期待していない HTTP status code です, got = %d, want = %d", resp.StatusCode, tc.WantHTTPStatusCode)
return
}
if tc.WantHTTPStatusCode != http.StatusOK {
return
}
var m map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&m); err != nil {
t.Error("レスポンスのデコードに失敗しました。", err)
return
}
v, ok := m["todo"]
if !ok {
t.Error("レスポンスの中にtodoがありません。")
return
}
got, ok := v.(map[string]interface{})
if !ok {
t.Error("レスポンスの中のtodoがmapではありません。")
return
}
want := map[string]interface{}{
"subject": tc.Subject,
"description": tc.Description,
}
now := time.Now().UTC()
fmt.Println(got)
fmt.Println(want)
diff := cmp.Diff(got, want, cmpopts.IgnoreMapEntries(func(k string, v interface{}) bool {
switch k {
case "id":
if vv, _ := v.(float64); vv == 0 {
t.Errorf("id を数値に変換できません, got = %s", k)
}
return true
case "created_at", "updated_at":
vv, ok := v.(string)
if !ok {
t.Errorf("日付が文字列に変換できません, got = %+v", k)
return true
}
if tt, err := time.Parse(time.RFC3339, vv); err != nil {
t.Errorf("日付が期待しているフォーマットではありません, got = %s", k)
} else if now.Before(tt) {
t.Errorf("日付が未来の日付になっています, got = %s", tt)
}
return true
}
return false
}))
if diff != "" {
t.Error("期待していない値です\n", diff)
}
})
}
}
type TODOHandler struct {
svc *service.TODOService
}
// NewTODOHandler returns TODOHandler based http.Handler.
func NewTODOHandler(svc *service.TODOService) *TODOHandler {
return &TODOHandler{
svc: svc,
}
}
func (h *TODOHandler)ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost{
var todo model.CreateTODORequest
if err := json.NewDecoder(r.Body).Decode(&todo); err != nil {
log.Fatal(err)
}
if todo.Subject == ""{
w.WriteHeader(400)
}else{
createdTodo, _ := h.Create(r.Context(),&todo)
e := json.NewEncoder(w)
if err:= e.Encode(createdTodo); err != nil{
log.Fatal("ListenAndServe:", err)
}
}
}
}
// Create handles the endpoint that creates the TODO.
func (h *TODOHandler) Create(ctx context.Context, req *model.CreateTODORequest) (*model.CreateTODOResponse, error) {
createtodo, err := h.svc.CreateTODO(ctx, req.Subject, req.Description)
if err != nil{
log.Fatal(err)
}
return &model.CreateTODOResponse{TODO: *createtodo}, nil
}
type TODOService struct {
db *sql.DB
}
// NewTODOService returns new TODOService.
func NewTODOService(db *sql.DB) *TODOService {
return &TODOService{
db: db,
}
}
// CreateTODO creates a TODO on DB.
func (s *TODOService) CreateTODO(ctx context.Context, subject, description string) (*model.TODO, error) {
const (
insert = `INSERT INTO todos(subject, description) VALUES(?, ?)`
confirm = `SELECT subject, description, created_at, updated_at FROM todos WHERE id = ?`
)
var todo model.TODO
if subject == ""{
msg := "subjectがありません"
return &todo,fmt.Errorf("err %s", msg)
}
stmt,err := s.db.PrepareContext(ctx,insert)
if err != nil{
log.Fatal("server/todo.go s.db.ExecContext(ctx,insert,subject,description)",err)
}
defer stmt.Close()
res, err := stmt.ExecContext(ctx, subject,description)
if err != nil {
return nil, err
}
insert_id,err := res.LastInsertId()
if err != nil{
log.Fatal("server/todo.go stmt.RowsAffected()",err)
}
err = s.db.QueryRowContext(ctx,confirm,insert_id).Scan(&todo.Subject,&todo.Description,&todo.CreatedAt,&todo.UpdatedAt)
if err != nil{
log.Fatal("server/todo.go s.db.QueryRowContext(ctx,confirm,insert_id).Scan(&todo.Subject,&todo.Description,&todo.CreatedAt,&todo.UpdatedAt)",err)
}
return &todo, err
}
I am building a TODO application with TDD and have a question. I get the error at the top. What kind of error is this and how can I improve it?

Could not `chan` pass through RPC in Go?

For the following code, get Message via RPC, why its Done field is nil?
Could not chan pass through RPC?
type Message struct {
Text string
Done chan bool
}
type (
GetArgs struct {
Key string
}
GetReply struct {
Message *Message
}
)
// Client
func connect() *rpc.Client {
client, err := rpc.Dial("tcp", ":1234")
if err != nil {
log.Fatal("dialing:", err)
}
return client
}
func get(key string) *Message {
client := connect()
args := GetArgs{key}
reply := GetReply{}
err := client.Call("KV.Get", &args, &reply)
if err != nil {
log.Fatal("error:", err)
}
client.Close()
return reply.Message
}
// Server
type KV struct {
mu sync.Mutex
messages map[string]*Message
}
func server() {
kv := new(KV)
kv.messages = map[string]*Message{}
done := make(chan bool)
kv.messages["Jan"] = &Message{Text: "hello", Done: done}
rpcs := rpc.NewServer()
rpcs.Register(kv)
l, err := net.Listen("tcp", ":1234")
if err != nil {
log.Fatal("error:", err)
}
go func() {
for {
conn, err := l.Accept()
if err == nil {
go rpcs.ServeConn(conn)
} else {
break
}
}
l.Close()
}()
}
func (kv *KV) Get(args *GetArgs, reply *GetReply) error {
kv.mu.Lock()
defer kv.mu.Unlock()
val, ok := kv.messages[args.Key]
if ok {
reply.Message = val
}
return nil
}
func main() {
server()
fmt.Printf("get() -> %v\n", get("Jan"))
// Output:
// get() -> &{hello <nil>}
}
No. RPC arguments can only be those that are serializable by encoding/gob, and chans are not one of them
Functions and channels will not be sent in a gob. Attempting to encode such a value at the top level will fail. A struct field of chan or func type is treated exactly like an unexported field and is ignored.

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 smtp get and parse wellcome message of server

I want to get welcome message of smtp server and check if contains specific keyword, this is my actual code:
package main
import (
"fmt"
"net"
"strings"
"time"
"net/smtp"
"errors"
)
type loginAuth struct {
username, password string
}
func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
return "LOGIN", []byte{}, nil
}
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
if more {
switch string(fromServer) {
case "Username:":
return []byte(a.username), nil
case "Password:":
return []byte(a.password), nil
default:
return nil, errors.New("Unkown fromServer")
}
}
return nil, nil
}
func main() {
// attempt a connection
conn, err := net.DialTimeout("tcp", "88.198.24.108:25", 15 * time.Second)
defer conn.Close()
if err == nil {
buf := make([]byte, 32, 32)
conn.Read(buf)
if strings.Contains(string(buf), "Haraka") {
fmt.Println("this server not working with this application")
}
}
client, err := smtp.NewClient(conn, "88.198.24.108")
if err != nil {
fmt.Println("1>>", err)
return
}
err = client.Auth(&loginAuth{"info#example.com", "123456"})
if err != nil {
fmt.Println("2>>", err)
return
} else {
fmt.Println("auth successfull")
}
}
The code return this error : 1>> short response: 18 ready
where i wrong ?

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