Skip to content

Instantly share code, notes, and snippets.

@krpors
Created October 28, 2013 16:12
Show Gist options
  • Save krpors/7199782 to your computer and use it in GitHub Desktop.
Save krpors/7199782 to your computer and use it in GitHub Desktop.
Multicast heartbeats etc.
package main
import (
"flag"
"fmt"
"net"
"os"
"time"
)
var flagAddress *string = flag.String("address", "239.100.100.100:31337", "multicast group and port")
var flagId *string = flag.String("id", "some-uuid", "identification of this instance")
var flagInterval *int = flag.Int("interval", 5000, "beacon interval in milliseconds")
type discoverData struct {
Addr string
Id string
}
func (self discoverData) ToPacket() string {
return fmt.Sprintf("%s %s", self.Addr, self.Id)
}
func ParseDiscoverData(buf *[]byte) (discoverData, error) {
retdata := discoverData{}
return retdata, nil
}
// Listens for beacons/heartbeats.
func listen(strAddress string, recvChan chan discoverData) error {
addr, err := net.ResolveUDPAddr("udp", strAddress)
if err != nil {
return err
}
conn, err := net.ListenMulticastUDP("udp", nil, addr)
if err != nil {
return err
}
fmt.Printf("Joining multicast group %v\n", addr)
for {
buf := make([]byte, 512)
conn.Read(buf)
d, err := ParseDiscoverData(&buf)
if err != nil {
fmt.Printf("Unable to parse data: %s\n", err)
continue
}
recvChan <- d
}
return nil
}
// Finds the first interface which support multicasting, and is also up.
func findMulticastInterface() (net.Interface, error) {
ifs, err := net.Interfaces()
if err != nil {
return net.Interface{}, err
}
for i, _ := range ifs {
// check if the interface in the current iteration supports multicasting, and is
// in fact up. Immediately just return the first one. Not sure if this'll be
// a good solution though.
if ifs[i].Flags&(net.FlagMulticast|net.FlagUp) == (net.FlagMulticast | net.FlagUp) {
return ifs[i], nil
}
}
return net.Interface{}, fmt.Errorf("no suitable interface found which supports multicasting")
}
// Gets a source IP to send the UDP heartbeats from. This is done by finding the first
// address which is found on the given interface, and then parsing that address using
// either the CIDR notation (i.e. a.b.c.d/x) or just the 'plain' notation, a.b.c.d. It will
// also work for IPv6 addresses. Will return the IP string, or an error when either parsing
// failed miserably.
func getSrcIP(intf net.Interface) (string, error) {
addrs, err := intf.Addrs()
if err != nil {
return "", err
}
wut := addrs[0].String()
// first try CIDR. This is usually to be found on *nixes. Windows for instance
// does not return the network portion. For example, Windows returns just
// 192.168.0.30, but Linux returns 192.168.0.30/24.
ip, _, err := net.ParseCIDR(wut)
if err != nil {
// try parsing normally
ip = net.ParseIP(wut)
if ip == nil {
// Well, we tried it. Bail out.
return "", fmt.Errorf("unable to parse IP")
}
}
return ip.String(), nil
}
// Sends a beacon, or a heartbeat. The heartbeat should be sent using the source IP of
// an interface that supports multicasting. An IP can be found using the findMulticastInterface()
// and the getSrcIP() functions. On Windows for instance, when a Vbox host adapter has also
// been installed, the heartbeats are sent on that adapter by default (well, at least on my
// box).
func startHeartbeat(addr string, srcIP string, interval time.Duration) {
remoteAddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
fmt.Println(err)
}
sourceAddr, err := net.ResolveUDPAddr("udp", srcIP+":0")
if err != nil {
fmt.Println(err)
}
fmt.Printf("Announcing ourselves now\n")
fmt.Printf("Interface used for beacon is %v\n", sourceAddr)
conn, err := net.DialUDP("udp", sourceAddr, remoteAddr)
defer conn.Close()
if err != nil {
fmt.Printf("Error occurred: %v\n", err)
fmt.Printf("Not sending a beacon!\n")
return
}
for {
conn.Write([]byte("LOL!"))
time.Sleep(interval)
}
}
func main() {
flag.Parse()
if !flag.Parsed() {
flag.PrintDefaults()
os.Exit(1)
}
intf, err := findMulticastInterface()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
srcIP, err := getSrcIP(intf)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
recvChan := make(chan discoverData)
go listen(*flagAddress, recvChan)
go startHeartbeat(*flagAddress, srcIP, time.Duration(*flagInterval)*time.Millisecond)
// keep listening for beacons, just print them out
for a := range recvChan {
fmt.Println("Got a heartbeat:", a)
}
}
@krpors
Copy link
Author

krpors commented Oct 28, 2013

Default on Linux the multicasted data is also looped back to the current interface, which may be undesirable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment