Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2022 PolyLAN
Copyright (c) 2023 PolyLAN

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
19 changes: 17 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
edit the list of game to your liking then run:
# UDP-Broadcast

## Intalling

`go install`

## Running

List the ports to forward in `games.csv` and run the forwarder with

```
docker build -t proxy . && docker run --net=host proxy $INTERFACES
go run . [interface 1] [interface 2] [interface 3]...
```

For example

```
go run . eth0.10 eth0.11 eth0.12
```

3 changes: 1 addition & 2 deletions games.csv
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
#port,name
17500,prout
54915,prout2
2350-2450,trackmania
7 changes: 7 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module udp_broadcast

go 1.20

require github.com/google/gopacket v1.1.19

require golang.org/x/sys v0.0.0-20190412213103-97732733099d // indirect
14 changes: 14 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
164 changes: 112 additions & 52 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,53 @@ package main

import (
"bufio"
"encoding/binary"
"errors"
"fmt"
"net"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/google/gopacket/pcap"
"github.com/google/gopacket/pcap"
"io"
"log"
"net"
"os"
"strconv"
"strings"
"time"
)

type Interface struct {
pcap.Interface
SendBuffer chan gopacket.Packet
}

var (
const (
commentPrefix byte = '#'
rangeSeparator string = "-"
retransmitBufferSize int32 = 1024
snapshot_len int32 = 2048
promiscuous bool = false
timeout time.Duration = 500 * time.Millisecond
proxyTTL uint8 = 64
)

type Interface struct {
pcap.Interface
SendBuffer chan gopacket.Packet
}

func InterfaceFromName(name string) (Interface, error) {
devices, err := pcap.FindAllDevs()
if err != nil {
return Interface{}, err
}
for _, device := range devices {
if device.Name == name {
return Interface{device, make(chan gopacket.Packet, retransmitBufferSize)}, nil
if device.Name != name {
continue
}

v4addresses := []pcap.InterfaceAddress{}
for _, address := range device.Addresses {
if address.Broadaddr != nil {
v4addresses = append(v4addresses, address)
}
}
device.Addresses = v4addresses
return Interface{device, make(chan gopacket.Packet, retransmitBufferSize)}, nil
}
return Interface{}, nil
return Interface{}, fmt.Errorf("Cannot find interface %s", name)
}
func portInPorts(port uint16, ports []uint16) bool {
for _, p := range ports {
Expand All @@ -51,47 +59,65 @@ func portInPorts(port uint16, ports []uint16) bool {
return false
}

func lastAddr(n *net.IPNet) (net.IP, error) { // works when the n is a prefix, otherwise...
if n.IP.To4() == nil {
return net.IP{}, errors.New("Does not support IPv6 addresses.")
}
ip := make(net.IP, len(n.IP.To4()))
binary.BigEndian.PutUint32(ip, binary.BigEndian.Uint32(n.IP.To4())|^binary.BigEndian.Uint32(net.IP(n.Mask).To4()))
return ip, nil
}

func bufferRetransmit(packet gopacket.Packet, destinationDevices []Interface, ports *[]uint16) {
ipLayer := packet.Layer(layers.LayerTypeIPv4)
udpLayer := packet.Layer(layers.LayerTypeUDP)
if (ipLayer != nil) && (udpLayer != nil) {
ipPacket, _ := ipLayer.(*layers.IPv4)
udpPacket, _ := udpLayer.(*layers.UDP)
if ipPacket.TTL != proxyTTL && portInPorts(uint16(udpPacket.DstPort), *ports) {

//if ipPacket.TTL != proxyTTL && portInPorts(uint16(udpPacket.DstPort), *ports) {

if portInPorts(uint16(udpPacket.DstPort), *ports) {
log.Printf("Forwarding %s:%d -> %s:%d\n", ipPacket.SrcIP, udpPacket.SrcPort, ipPacket.DstIP, udpPacket.DstPort)
for _, destinationDevice := range destinationDevices {
fmt.Printf("%s:%d -> %s:%d\n", ipPacket.SrcIP, udpPacket.SrcPort, ipPacket.DstIP, udpPacket.DstPort)
fmt.Println("sending to ", destinationDevice.Name)
destinationDevice.SendBuffer <- packet
if ipPacket.DstIP != nil {
destinationDevice.SendBuffer <- packet
} else {
log.Printf("Didn't sent (no Dest IP) to %s\n", destinationDevice.Name)
}
}
}
}
}

func getRealBroadcastAddresses(device Interface) []net.IP{
brdAddrs := []net.IP{}
for _, address := range device.Addresses {
if address.Broadaddr.Equal(address.IP) {
//TODO: pcap lib seems to return invalid broadcast address
// if the underlying interface defines more than one ip address.
// In those case, the Broadcast address will be equal to the ip address.
continue
}
brdAddrs = append(brdAddrs, address.Broadaddr)
}
return brdAddrs
}


func interfaceWorker(device Interface, destinationDevices []Interface, ports *[]uint16) error {
// Open device
handle, err := pcap.OpenLive(device.Name, snapshot_len, promiscuous, timeout)
if err != nil {
return err
}
defer handle.Close()

bpfFilter := "udp and (dst host 255.255.255.255"
for _, address := range device.Addresses {
fmt.Println(address.Broadaddr)
bpfFilter = bpfFilter + " or dst host " + address.Broadaddr.String()
for _, address := range getRealBroadcastAddresses(device) {
bpfFilter += " or dst host " + address.String()
}
bpfFilter = bpfFilter + ")"
bpfFilter += ")"
handle.SetBPFFilter(bpfFilter)
fmt.Println("using filter '", bpfFilter, "'")
fmt.Println("for interface", device, "->", destinationDevices)
deviceNames:= []string{}
for _, d := range destinationDevices {
deviceNames = append(deviceNames, d.Name)
}


log.Printf("Using filter '%s' for interface %s -> %v with %v addresses\n", bpfFilter, device.Name, deviceNames, device.Addresses)

packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
for {
select {
Expand All @@ -102,24 +128,30 @@ func interfaceWorker(device Interface, destinationDevices []Interface, ports *[]
//send Packet and rewrite the destAddr
ipLayer := outgoingPacket.Layer(layers.LayerTypeIPv4)
udpLayer := outgoingPacket.Layer(layers.LayerTypeUDP)

if ipLayer != nil && udpLayer != nil {
ipPacket, _ := ipLayer.(*layers.IPv4)
udpPacket, _ := udpLayer.(*layers.UDP)
for _, address := range device.Addresses {
ipPacket.DstIP = address.Broadaddr
ipPacket.TTL = proxyTTL

for _, address := range getRealBroadcastAddresses(device) {

ipPacket.DstIP = address
ipPacket.TTL -= 1
options := gopacket.SerializeOptions{
ComputeChecksums: true,
FixLengths: true,
}
udpPacket.SetNetworkLayerForChecksum(ipPacket)
newBuffer := gopacket.NewSerializeBuffer()
err := gopacket.SerializePacket(newBuffer, options, outgoingPacket)
if err != nil {
return err
}
outgoingPacket := newBuffer.Bytes()
handle.WritePacketData(outgoingPacket)

newBuffer := gopacket.NewSerializeBuffer()
log.Printf("Sending %s:%d on %s", ipPacket.DstIP, udpPacket.SrcPort, device.Name)
err := gopacket.SerializePacket(newBuffer, options, outgoingPacket)
if err != nil {
log.Println(err)
continue
}
outgoingPacket := newBuffer.Bytes()
handle.WritePacketData(outgoingPacket)
}
}
}
Expand All @@ -132,8 +164,12 @@ func readPortList(confFile string) ([]uint16, error) {
if err != nil {
return nil, err
}
ports := make([]uint16, 0)
ports := []uint16{}
lineReader := bufio.NewReader(csvFile)
if lineReader == nil {
log.Fatal(err)
}

for {
line, _, err := lineReader.ReadLine()
if err != nil {
Expand All @@ -145,32 +181,56 @@ func readPortList(confFile string) ([]uint16, error) {

}
lineStr := string(line)
if len(lineStr) > 0 && lineStr[0] == '#' {

// Skip empty line and comment (#)
if len(lineStr) > 0 && lineStr[0] == commentPrefix {
log.Println(lineStr)
continue
}

columns := strings.SplitN(lineStr, ",", 2)
port, err := strconv.ParseUint(columns[0], 10, 16)
log.Println(lineStr)
log.Println(columns[0])
s := strings.Split(columns[0], rangeSeparator)
if len(s) == 1 {
s = append(s, s[0])
}

start, err := strconv.ParseUint(s[0], 10, 16)
if err != nil {
log.Println(err)
continue
return nil, err
}
end, err := strconv.ParseUint(s[1], 10, 16)
if err != nil {
return nil, err
}
ports = append(ports, uint16(port))

for i := start; i <= end; i++ {
ports = append(ports, uint16(i))
}

}
return ports, nil
}

func main() {
if len(os.Args) == 2 && os.Args[1] == "-h" {
fmt.Println("Usage: ", os.Args[0], "[INTERFACE] ...")
log.Println("Usage: ", os.Args[0], "[INTERFACE] ...")
return
}
ports, err := readPortList("games.csv")
log.Printf("Ports founds %v\n", ports)
if err != nil {
log.Fatal(err)
}
devices := []Interface{}
for _, deviceName := range os.Args[1:] {
device, _ := InterfaceFromName(deviceName)
device, err := InterfaceFromName(deviceName)

if err != nil {
log.Fatal(err)
}

devices = append(devices, device)
}

Expand All @@ -185,7 +245,7 @@ func main() {
}
}
}
fmt.Println("starting worker on", v.Name)
log.Println("starting worker on", v.Name)
if i == len(devices)-1 {
interfaceWorker(v, otherDevices, &ports)
} else {
Expand Down