How to convert VP8 interframe into image with Pion/Webrtc? - go

I have tried snapshot example to convert VP8 video stream to jpeg in Pion/Webrtc examples.
It works well, but interframes are not included. An error message displayed when I was fixing it.
vp8: Golden / AltRef frames are not implemented.
This error comes from golang's vp8 package.
The modified code is shown below:
if !videoKeyframe {
decoder.Init(bytes.NewReader(sample.Data), len(sample.Data))
_, err := decoder.DecodeFrameHeader()
if err != nil {
fmt.Printf("Header Error: %s\n", err)
continue
}
_, err2 := decoder.DecodeFrame()
if err2 != nil {
fmt.Printf("DecodeFrame Error: %s\n", err2)
continue
}
}
I also tried to modify gstreamer-receive example. But it's hard for me to do in C.
Is there anyway to convert interframe to jpeg easily ?

Finally, I solved with libvpx-go
I modified code as shown below.
package main
import (
"bytes"
"fmt"
"image/jpeg"
// "log"
"os"
"github.com/pion/rtp"
"github.com/pion/rtp/codecs"
"github.com/pion/webrtc/v3/pkg/media/samplebuilder"
"github.com/xlab/libvpx-go/vpx"
)
// type Frame struct {
// *image.RGBA
// Timecode time.Duration
// IsKeyframe bool
// }
type VDecoder struct {
enabled bool
src <-chan *rtp.Packet
ctx *vpx.CodecCtx
iface *vpx.CodecIface
}
type VCodec string
const (
CodecVP8 VCodec = "V_VP8"
CodecVP9 VCodec = "V_VP9"
CodecVP10 VCodec = "V_VP10"
)
func NewVDecoder(codec VCodec, src <-chan *rtp.Packet) *VDecoder {
dec := &VDecoder{
src: src,
ctx: vpx.NewCodecCtx(),
}
switch codec {
case CodecVP8:
dec.iface = vpx.DecoderIfaceVP8()
case CodecVP9:
dec.iface = vpx.DecoderIfaceVP9()
default: // others are currently disabled
log.Println("[WARN] unsupported VPX codec:", codec)
return dec
}
err := vpx.Error(vpx.CodecDecInitVer(dec.ctx, dec.iface, nil, 0, vpx.DecoderABIVersion))
if err != nil {
log.Println("[WARN]", err)
return dec
}
dec.enabled = true
return dec
}
func (v *VDecoder) Save(savePath string) { //, out chan<- Frame
// defer close(out)
i := 0
sampleBuilder := samplebuilder.New(20000, &codecs.VP8Packet{}, 90000)
for pkt := range v.src {
sampleBuilder.Push(pkt)
// Use SampleBuilder to generate full picture from many RTP Packets
sample := sampleBuilder.Pop()
if sample == nil {
continue
}
if !v.enabled {
continue
}
dataSize := uint32(len(sample.Data))
err := vpx.Error(vpx.CodecDecode(v.ctx, string(sample.Data), dataSize, nil, 0))
if err != nil {
log.Println("[WARN]", err)
continue
}
var iter vpx.CodecIter
img := vpx.CodecGetFrame(v.ctx, &iter)
if img != nil {
img.Deref()
// out <- Frame{
// RGBA: img.ImageRGBA(),
// Timecode: time.Duration(pkt.Timestamp),
// }
i++
buffer := new(bytes.Buffer)
if err = jpeg.Encode(buffer, img.ImageYCbCr(), nil); err != nil {
// panic(err)
fmt.Printf("jpeg Encode Error: %s\r\n", err)
}
fo, err := os.Create(fmt.Sprintf("%s%d%s", savePath, i, ".jpg"))
if err != nil {
fmt.Printf("image create Error: %s\r\n", err)
//panic(err)
}
// close fo on exit and check for its returned error
defer func() {
if err := fo.Close(); err != nil {
panic(err)
}
}()
if _, err := fo.Write(buffer.Bytes()); err != nil {
fmt.Printf("image write Error: %s\r\n", err)
//panic(err)
}
fo.Close()
}
}
}
Just add 2 lines of code to save sequential images
vd := NewVDecoder(CodecVP8, rtpChan)
go vd.Save("/ramdisk/image_seq/xxx")
Enjoy!
P.S. don't forget to install libvpx-go 's dependencies before running go get and make sure libvpx 's version is 1.8

If you need to convert interframe to jpeg the only way would be to decode every frame from your stream. I don't think there is any other way as interframes depend on previous frames.

Sync Solution
This is an alternative version of the same solution of CK So
It is based on libvpx-go but dose not use channels.
package main
import (
"bytes"
"fmt"
"image/jpeg"
"log"
// "log"
"os"
"github.com/xlab/libvpx-go/vpx"
)
// type Frame struct {
// *image.RGBA
// Timecode time.Duration
// IsKeyframe bool
// }
type VDecoderSync struct {
enabled bool
//src <-chan []byte this blocked the thread even if fed.
ctx *vpx.CodecCtx
iface *vpx.CodecIface
i int
}
type VCodecSync string
const (
CodecVP8Sync VCodecSync = "V_VP8"
CodecVP9Sync VCodecSync = "V_VP9"
CodecVP10Sync VCodecSync = "V_VP10"
)
func NewVDecoderSync(codec VCodecSync) *VDecoderSync {
dec := &VDecoderSync{
ctx: vpx.NewCodecCtx(),
}
switch codec {
case CodecVP8Sync:
dec.iface = vpx.DecoderIfaceVP8()
case CodecVP9Sync:
dec.iface = vpx.DecoderIfaceVP9()
default: // others are currently disabled
log.Println("[WARN] unsupported VPX codec:", codec)
return dec
}
err := vpx.Error(vpx.CodecDecInitVer(dec.ctx, dec.iface, nil, 0, vpx.DecoderABIVersion))
if err != nil {
log.Println("[WARN]", err)
return dec
}
dec.enabled = true
return dec
}
func (v *VDecoderSync) EncodeToJson(savePath string, pkt []byte, print_to_file bool) []byte {
sample := pkt
dataSize := uint32(len(sample))
err := vpx.Error(vpx.CodecDecode(v.ctx, string(sample), dataSize, nil, 0))
if err != nil {
log.Println("[WARN]", err)
return nil
}
var iter vpx.CodecIter
img := vpx.CodecGetFrame(v.ctx, &iter)
if img != nil {
img.Deref()
// out <- Frame{
// RGBA: img.ImageRGBA(),
// Timecode: time.Duration(pkt.Timestamp),
// }
buffer := new(bytes.Buffer)
if err = jpeg.Encode(buffer, img.ImageYCbCr(), nil); err != nil {
// panic(err)
fmt.Printf("jpeg Encode Error: %s\r\n", err)
}
bytes_to_send := buffer.Bytes()
if print_to_file {
v.i++
v.i = v.i % 30
fo, err := os.Create(fmt.Sprintf("%s%d%s", savePath, v.i, ".jpg"))
if err != nil {
fmt.Printf("image create Error: %s\r\n", err)
//panic(err)
}
// close fo on exit and check for its returned error
defer func() {
if err := fo.Close(); err != nil {
panic(err)
}
}()
if _, err := fo.Write(bytes_to_send); err != nil {
fmt.Printf("image write Error: %s\r\n", err)
//panic(err)
}
}
return bytes_to_send
}
return nil
}

Related

Golang multipart file form request

I'm writing an API client against Mapbox, uploading a batch of svg images to a custom map. The api they provide for this is documented with an example cUrl call that works fine:
curl -F images=#include/mapbox/sprites_dark/aubergine_selected.svg "https://api.mapbox.com/styles/v1/<my_company>/<my_style_id>/sprite?access_token=$MAPBOX_API_KEY" --trace-ascii /dev/stdout
When attemting to do the same from golang I quickly came across that the multiform library is very limited, and wrote some code to make the request as similar to the cUrl request mentioned above.
func createMultipartFormData(fileMap map[string]string) (bytes.Buffer, *multipart.Writer) {
var b bytes.Buffer
var err error
w := multipart.NewWriter(&b)
var fw io.Writer
for fileName, filePath := range fileMap {
h := make(textproto.MIMEHeader)
h.Set("Content-Disposition",
fmt.Sprintf(`form-data; name="%s"; filename="%s"`, "images", fileName))
h.Set("Content-Type", "image/svg+xml")
if fw, err = w.CreatePart(h); err != nil {
fmt.Printf("Error creating form File %v, %v", fileName, err)
continue
}
fileContents, err := ioutil.ReadFile(filePath)
fileContents = bytes.ReplaceAll(fileContents, []byte("\n"), []byte("."))
blockSize := 64
remainder := len(fileContents) % blockSize
iterations := (len(fileContents) - remainder) / blockSize
newBytes := []byte{}
for i := 0; i < iterations; i++ {
start := i * blockSize
end := i*blockSize + blockSize
newBytes = append(newBytes, fileContents[start:end]...)
newBytes = append(newBytes, []byte("\n")...)
}
if remainder > 0 {
newBytes = append(newBytes, fileContents[iterations*blockSize:]...)
newBytes = append(newBytes, []byte("\n")...)
}
if err != nil {
fmt.Printf("Error reading svg file: %v: %v", filePath, err)
continue
}
_, err = fw.Write(newBytes)
if err != nil {
log.Debugf("Could not write file to multipart: %v, %v", fileName, err)
continue
}
}
w.Close()
return b, w
}
Along with setting the headers in the actual request:
bytes, formWriter := createMultipartFormData(filesMap)
req, err := http.NewRequest("Post", fmt.Sprintf("https://api.mapbox.com/styles/v1/%v/%v/sprite?access_token=%v", "my_company", styleID, os.Getenv("MAPBOX_API_KEY")), &bytes)
if err != nil {
return err
}
req.Header.Set("User-Agent", "curl/7.64.1")
req.Header.Set("Accept", "*/*")
req.Header.Set("Content-Length", fmt.Sprintf("%v", len(bytes.Bytes())))
req.Header.Set("Content-Type", formWriter.FormDataContentType())
byts, _ := httputil.DumpRequest(req, true)
fmt.Println(string(byts))
res, err := http.DefaultClient.Do(req)
Even want as far to limit the line length and replicate the encoding used by cUrl but so far no luck. Does anyone with experience know why this works from cUrl but not golang?
Well, I admit that all the parts of the "puzzle" to solve your task can be found on the 'net in abundance, there are two problems with this:
They quite often miss certain interesting details.
Sometimes, they give outright incorrect advice.
So, here's a working solution.
package main
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"mime"
"mime/multipart"
"net/http"
"net/textproto"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
)
func main() {
const (
dst = "https://api.mapbox.com/styles/v1/AcmeInc/Style_001/sprite"
fname = "path/to/a/sprite/image.svg"
token = "an_invalid_token"
)
err := post(dst, fname, token)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func post(dst, fname, token string) error {
u, err := url.Parse(dst)
if err != nil {
return fmt.Errorf("failed to parse destination url: %w", err)
}
form, err := makeRequestBody(fname)
if err != nil {
return fmt.Errorf("failed to prepare request body: %w", err)
}
q := u.Query()
q.Set("access_token", token)
u.RawQuery = q.Encode()
hdr := make(http.Header)
hdr.Set("Content-Type", form.contentType)
req := http.Request{
Method: "POST",
URL: u,
Header: hdr,
Body: ioutil.NopCloser(form.body),
ContentLength: int64(form.contentLen),
}
resp, err := http.DefaultClient.Do(&req)
if err != nil {
return fmt.Errorf("failed to perform http request: %w", err)
}
defer resp.Body.Close()
_, _ = io.Copy(os.Stdout, resp.Body)
return nil
}
type form struct {
body *bytes.Buffer
contentType string
contentLen int
}
func makeRequestBody(fname string) (form, error) {
ct, err := getImageContentType(fname)
if err != nil {
return form{}, fmt.Errorf(
`failed to get content type for image file "%s": %w`,
fname, err)
}
fd, err := os.Open(fname)
if err != nil {
return form{}, fmt.Errorf("failed to open file to upload: %w", err)
}
defer fd.Close()
stat, err := fd.Stat()
if err != nil {
return form{}, fmt.Errorf("failed to query file info: %w", err)
}
hdr := make(textproto.MIMEHeader)
cd := mime.FormatMediaType("form-data", map[string]string{
"name": "images",
"filename": fname,
})
hdr.Set("Content-Disposition", cd)
hdr.Set("Contnt-Type", ct)
hdr.Set("Content-Length", strconv.FormatInt(stat.Size(), 10))
var buf bytes.Buffer
mw := multipart.NewWriter(&buf)
part, err := mw.CreatePart(hdr)
if err != nil {
return form{}, fmt.Errorf("failed to create new form part: %w", err)
}
n, err := io.Copy(part, fd)
if err != nil {
return form{}, fmt.Errorf("failed to write form part: %w", err)
}
if int64(n) != stat.Size() {
return form{}, fmt.Errorf("file size changed while writing: %s", fd.Name())
}
err = mw.Close()
if err != nil {
return form{}, fmt.Errorf("failed to prepare form: %w", err)
}
return form{
body: &buf,
contentType: mw.FormDataContentType(),
contentLen: buf.Len(),
}, nil
}
var imageContentTypes = map[string]string{
"png": "image/png",
"jpg": "image/jpeg",
"jpeg": "image/jpeg",
"svg": "image/svg+xml",
}
func getImageContentType(fname string) (string, error) {
ext := filepath.Ext(fname)
if ext == "" {
return "", fmt.Errorf("file name has no extension: %s", fname)
}
ext = strings.ToLower(ext[1:])
ct, found := imageContentTypes[ext]
if !found {
return "", fmt.Errorf("unknown file name extension: %s", ext)
}
return ct, nil
}
Some random notes on implementation to help you understands the concepts:
To construct the request's payload (body), we use a bytes.Buffer instance.
It has a nice property in that a pointer to it (*bytes.Buffer) implements both io.Writer and io.Reader and hence can be easily composed with other parts of the Go stdlib which deal with I/O.
When preparing a multipart form for sending, we do not slurp the whole file's contents into memory but instead "pipe" them right into the "mutipart form writer".
We have a lookup table which maps the extension of a file name to submit to its MIME type; I have no idea whether this is needed by the API or not; if it's not really required, the part of the code which prepares a form's field containing a file could be simplified a lot, but cURL send it, so do we.
Just to be curious, What is this for?
fileContents = bytes.ReplaceAll(fileContents, []byte("\n"), []byte("."))
blockSize := 64
remainder := len(fileContents) % blockSize
iterations := (len(fileContents) - remainder) / blockSize
newBytes := []byte{}
for i := 0; i < iterations; i++ {
start := i * blockSize
end := i*blockSize + blockSize
newBytes = append(newBytes, fileContents[start:end]...)
newBytes = append(newBytes, []byte("\n")...)
}
if remainder > 0 {
newBytes = append(newBytes, fileContents[iterations*blockSize:]...)
newBytes = append(newBytes, []byte("\n")...)
}
if err != nil {
fmt.Printf("Error reading svg file: %v: %v", filePath, err)
continue
}
Reading a entire file into memory is rarely a good idea (ioutil.ReadFile).
As #muffin-top says, how about those three lines of code?
for fileName, filePath := range fileMap {
// h := ...
fw, _ := w.CreatePart(h) // TODO: handle error
f, _ := os.Open(filePath) // TODO: handle error
io.Copy(fw, f) // TODO: handle error
f.Close() // TODO: handle error
}

binary-to-text encoding that replace a byte into a predefined unique string in golang

I am trying to build a binary-to-text encoder with the ability to replace every byte into a predefined unique string and then use the same string set to decode back into binary.
I am able to make the encoder and decoder for simple .txt files but I want to make this workable for .zip files too.
Encoder :
package main
import (
"archive/zip"
"bufio"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"os"
"strings"
)
func main() {
// keys.json is a 256 words dictionary for every byte
keysFile, err := os.Open("keys.json")
if err != nil {
log.Printf("unable to read keys.json file , error : %v", err)
return
}
var keys []string
byteValue, _ := ioutil.ReadAll(keysFile)
if err := json.Unmarshal(byteValue, &keys); err != nil {
log.Printf("unable to unmarshal array , error : %v", err)
return
}
Encoder(keys)
}
func listFiles(file *zip.File) ([]byte, error) {
fileToRead, err := file.Open()
if err != nil {
msg := "Failed to open zip %s for reading: %s"
return nil, fmt.Errorf(msg, file.Name, err)
}
b := make([]byte, file.FileInfo().Size())
fileToRead.Read(b)
defer fileToRead.Close()
if err != nil {
return nil, fmt.Errorf("failed to read zip file : %s for reading , error : %s", file.Name, err)
}
return b, nil
}
func Encoder(keys []string) {
read, err := zip.OpenReader("some.zip")
if err != nil {
msg := "Failed to open: %s"
log.Fatalf(msg, err)
}
defer read.Close()
var encodedBytes []byte
f, err := os.Create("result.txt")
w := bufio.NewWriter(f)
defer f.Close()
for _, file := range read.File {
readBytes, err := listFiles(file)
if err != nil {
log.Fatalf("Failed to read %s from zip: %s", file.Name, err)
continue
}
for i, b := range readBytes {
for _, eb := range []byte(keys[b] + " ") {
encodedBytes = append(encodedBytes, eb)
}
}
}
_, err = w.Write(encodedBytes)
if err != nil {
log.Printf("error :%v", err)
return
}
}
Decoder :
func Decoder(keys []string) {
inputFile, err := os.Open("result.txt")
if err != nil {
log.Printf("unable to read file , error : %v", err)
return
}
inputBytes, _ := ioutil.ReadAll(inputFile)
var (
current []byte
decoded []byte
)
for _, c := range inputBytes {
if c != 32 {
current = append(current, c)
} else {
for i, key := range keys {
if string(current) == key {
decoded = append(decoded, byte(i))
break
}
}
current = []byte{}
}
}
// here i want the decoded back into zip file
}
here is a similar one in nodejs.
Two things:
You are dealing with spaces correctly, but not with newlines.
Your decoder loop is wrong. As far as I can tell, it should look like the following:
for i, key := range keys {
if string(current)==key {
decoded=append(decoded,i)
break
}
}
Also, your decoded is an int-array, not a byte-array.

How should I encode an audio stream for Google Voice recognition in golang?

I'm building a discord bot that uses an Opus stream. I've tried various things such as sending the Opus packets up directly, decoding the OPUS stream to a PCM and encoding it into a byte array, and converting the PCM to a byte array directly. In all cases I get:
Could not recognize: code:11 message:"Audio data is being streamed too slow. Please stream audio data approximately at real time."
I've tried 8kHz-48kHz frequencies at 20 ms frames. I've also tried to encode the converted PCM with the max bitrate. I have run the sample code successfully, so there is no connection issue on my end. Where should I look for a solution?
package main
import (
"fmt"
//"io"
"log"
"os"
"flag"
speech "cloud.google.com/go/speech/apiv1"
"golang.org/x/net/context"
speechpb "google.golang.org/genproto/googleapis/cloud/speech/v1"
"github.com/bwmarrin/discordgo"
"layeh.com/gopus"
)
// OnError gets called by dgvoice when an error is encountered.
// By default logs to STDERR
var OnError = func(str string, err error) {
prefix := "dgVoice: " + str
if err != nil {
os.Stderr.WriteString(prefix + ": " + err.Error())
} else {
os.Stderr.WriteString(prefix)
}
}
var stream speechpb.Speech_StreamingRecognizeClient
func main() {
var (
Token = flag.String("t", "", "Discord bot token.")
// Email = flag.String("e", "", "Discord account email.")
// Password = flag.String("p", "", "Discord account password.")
GuildID = flag.String("g", "", "Guild ID")
ChannelID = flag.String("c", "", "Channel ID")
)
flag.Parse()
fmt.Println("Connecting to Discord...")
// Connect to Discord
discord, err := discordgo.New(*Token)
if err != nil {
fmt.Println(err)
return
}
fmt.Println("Opening Socket...")
// Open Websocket
err = discord.Open()
if err != nil {
fmt.Println(err)
return
}
fmt.Println("Joining Channel...")
// Connect to voice channel.
// NOTE: Setting mute to false, deaf to true.
dgv, err := discord.ChannelVoiceJoin(*GuildID, *ChannelID, false, false)
if err != nil {
fmt.Println(err)
return
}
fmt.Println("Connecting to Google Speech Recognition API...")
ctx := context.Background()
// [START speech_streaming_mic_recognize]
client, err := speech.NewClient(ctx)
if err != nil {
log.Fatal(err)
}
stream, err = client.StreamingRecognize(ctx)
if err != nil {
log.Fatal(err)
}
// Send the initial configuration message.
if err := stream.Send(&speechpb.StreamingRecognizeRequest{
StreamingRequest: &speechpb.StreamingRecognizeRequest_StreamingConfig{
StreamingConfig: &speechpb.StreamingRecognitionConfig{
Config: &speechpb.RecognitionConfig{
Encoding: speechpb.RecognitionConfig_LINEAR16,
SampleRateHertz: 16000,
LanguageCode: "en-US",
},
//InterimResults: true,
SingleUtterance: true,
},
},
}); err != nil {
log.Fatal(err)
}
recv := make(chan *discordgo.Packet, 2)
go Receive(dgv, recv)
send := make(chan []int16, 2)
go Send(dgv, send)
// dgv.Speaking(true)
// defer dgv.Speaking(false)
go func() {
for {
p, ok := <-recv
if !ok {
fmt.Println("Not OK")
return
}
send <- p.PCM
}
} ()
for {
resp, err := stream.Recv()
//fmt.Printf("%+v\n",resp)
if err != nil {
log.Fatalf("Cannot stream results: %v", err)
}
if err := resp.Error; err != nil {
log.Fatalf("Could not recognize: %v", err)
}
for _, result := range resp.Results {
fmt.Printf("Result: %+v\n", result)
}
}
// Close connections
dgv.Close()
discord.Close()
return
}
func Receive(v *discordgo.VoiceConnection, c chan *discordgo.Packet) {
var speakers map[uint32]*gopus.Decoder
if c == nil {
return
}
var err error
for {
p, ok := <-v.OpusRecv
if !ok {
return
}
if speakers == nil {
speakers = make(map[uint32]*gopus.Decoder)
}
_, ok = speakers[p.SSRC]
if !ok {
speakers[p.SSRC], err = gopus.NewDecoder(16000, 1)
if err != nil {
OnError("error creating opus decoder", err)
continue
}
}
p.PCM, err = speakers[p.SSRC].Decode(p.Opus, 320, false)
if err != nil {
OnError("Error decoding opus data", err)
continue
}
// try encoding pcm frame with Opus
c <- p
}
}
func Send(v *discordgo.VoiceConnection, pcm <- chan []int16) {
for {
// read pcm from chan, exit if channel is closed.
recv, ok := <-pcm
if !ok {
OnError("PCM Channel closed", nil)
return
}
buf := make([]byte,2*len(recv))
for i := 0; i < len(recv); i+=2 {
var h, l uint8 = uint8(i>>8), uint8(i&0xff)
buf[i] = h
buf[i+1] = l
}
stream.Send(&speechpb.StreamingRecognizeRequest{
StreamingRequest: &speechpb.StreamingRecognizeRequest_AudioContent{
AudioContent: buf,
},
});
}
}
The Google Speech-to-Text documentation has a fully working example of streaming speech recognition in Go.
"Audio data is being streamed too slow" is sent by the server when it is not receiving audio in realtime. In this case, the above code contains a bug that results in only half a PCM frame being sent on each iteration of the Send loop:
for i := 0; i < len(recv); i+=2 {
var h, l uint8 = uint8(i>>8), uint8(i&0xff)
buf[i] = h
buf[i+1] = l
}
recv is a slice of int16 values, so it should be iterated over one value at a time, not i+=2, which skips every other value. buf is a uint8 slice, so the indexing for that is valid.

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
}

Getting EOF from server as client in Go

I have some a Go client for a custom protocol. The protocol is lz4-compressed JSON-RPC with a four byte header giving the length of the compressed JSON.
func ReceiveMessage(conn net.Conn) ([]byte, error) {
start := time.Now()
bodyLen := 0
body := make([]byte, 0, 4096)
buf := make([]byte, 0, 256)
for bodyLen == 0 || len(body) < bodyLen {
if len(body) > 4 {
header := body[:4]
body = body[:4]
bodyLen = int(unpack(header))
}
n, err := conn.Read(buf[:])
if err != nil {
if err != io.EOF {
return body, err
}
}
body = append(body, buf[0:n]...)
now := time.Now()
if now.Sub(start) > time.Duration(readTimeout) * time.Millisecond {
return body, fmt.Errorf("Timed-out while reading from socket.")
}
time.Sleep(time.Duration(1) * time.Millisecond)
}
return lz4.Decode(nil, body)
}
The client:
func main() {
address := os.Args[1]
msg := []byte(os.Args[2])
fmt.Printf("Sending %s to %s\n", msg, address)
conn, err := net.Dial(address)
if err != nil {
fmt.Printf("%v\n", err)
return
}
// Another library call
_, err = SendMessage(conn, []byte(msg))
if err != nil {
fmt.Printf("%v\n", err)
return
}
response, err := ReceiveMessage(conn)
conn.Close()
if err != nil {
fmt.Printf("%v\n", err)
return
}
fmt.Printf("Response: %s\n", response)
}
When I call it, I get no response and it just times out. (If I do not explicitly ignore the EOF, it returns there with io.EOF error.) I have another library for this written in Python that also works against the same endpoint with the same payload. Do you see anything immediately?
[JimB just beat me to an answer but here goes anyway.]
The root issue is that you did body = body[:4]
when you wanted body = body[4:].
The former keeps only the first four header bytes
while the latter tosses
the four header bytes just decoded.
Here is a self contained version with some debug logs
that works.
It has some of the other changes I mentioned.
(I guessed at various things that you didn't include, like the lz4 package used, the timeout, unpack, etc.)
package main
import (
"encoding/binary"
"errors"
"fmt"
"io"
"log"
"net"
"time"
"github.com/bkaradzic/go-lz4"
)
const readTimeout = 30 * time.Second // XXX guess
func ReceiveMessage(conn net.Conn) ([]byte, error) {
bodyLen := 0
body := make([]byte, 0, 4096)
var buf [256]byte
conn.SetDeadline(time.Now().Add(readTimeout))
defer conn.SetDeadline(time.Time{}) // disable deadline
for bodyLen == 0 || len(body) < bodyLen {
if bodyLen == 0 && len(body) >= 4 {
bodyLen = int(unpack(body[:4]))
body = body[4:]
if bodyLen <= 0 {
return nil, errors.New("invalid body length")
}
log.Println("read bodyLen:", bodyLen)
continue
}
n, err := conn.Read(buf[:])
body = append(body, buf[:n]...)
log.Printf("appended %d bytes, len(body) now %d", n, len(body))
// Note, this is checked *after* handing any n bytes.
// An io.Reader is allowed to return data with an error.
if err != nil {
if err != io.EOF {
return nil, err
}
break
}
}
if len(body) != bodyLen {
return nil, fmt.Errorf("got %d bytes, expected %d",
len(body), bodyLen)
}
return lz4.Decode(nil, body)
}
const address = ":5678"
var msg = []byte(`{"foo":"bar"}`)
func main() {
//address := os.Args[1]
//msg := []byte(os.Args[2])
fmt.Printf("Sending %s to %s\n", msg, address)
conn, err := net.Dial("tcp", address)
if err != nil {
fmt.Printf("%v\n", err)
return
}
// Another library call
_, err = SendMessage(conn, msg)
if err != nil {
fmt.Printf("%v\n", err)
return
}
response, err := ReceiveMessage(conn)
conn.Close()
if err != nil {
fmt.Printf("%v\n", err)
return
}
fmt.Printf("Response: %s\n", response)
}
// a guess at what your `unpack` does
func unpack(b []byte) uint32 {
return binary.LittleEndian.Uint32(b)
}
func SendMessage(net.Conn, []byte) (int, error) {
// stub
return 0, nil
}
func init() {
// start a simple test server in the same process as a go-routine.
ln, err := net.Listen("tcp", address)
if err != nil {
log.Fatal(err)
}
go func() {
defer ln.Close()
for {
conn, err := ln.Accept()
if err != nil {
log.Fatalln("accept:", err)
}
go Serve(conn)
}
}()
}
func Serve(c net.Conn) {
defer c.Close()
// skip readding the initial request/message and just respond
const response = `{"somefield": "someval"}`
// normally (de)compression in Go is done streaming via
// an io.Reader or io.Writer but we need the final length.
data, err := lz4.Encode(nil, []byte(response))
if err != nil {
log.Println("lz4 encode:", err)
return
}
log.Println("sending len:", len(data))
if err = binary.Write(c, binary.LittleEndian, uint32(len(data))); err != nil {
log.Println("writing len:", err)
return
}
log.Println("sending data")
if _, err = c.Write(data); err != nil {
log.Println("writing compressed response:", err)
return
}
log.Println("Serve done, closing connection")
}
Playground (but not runnable there).
You have a number of issues with the server code. Without a full reproducing case, it's hard to tell if these will fix everything.
for bodyLen == 0 || len(body) < bodyLen {
if len(body) > 4 {
header := body[:4]
body = body[:4]
bodyLen = int(unpack(header))
}
every iteration, if len(body) > 4, you slice body back to the first 4 bytes. Body might never get to be >= bodyLen.
n, err := conn.Read(buf[:])
You don't need to re-slice buf here, use conn.Read(buf)
if err != nil {
if err != io.EOF {
return body, err
}
}
io.EOF is the end of the stream, and you need to handle it. Note that n might still be > 0 when you get an EOF. Check after processing the body for io.EOF or you could loop indefinitely.
body = append(body, buf[0:n]...)
now := time.Now()
if now.Sub(start) > time.Duration(readTimeout) * time.Millisecond {
return body, fmt.Errorf("Timed-out while reading from socket.")
you would be better off using conn.SetReadDeadline before each read, so a stalled Read could be interrupted.

Resources