diff --git a/LICENSE b/LICENSE index ac11630..66e453b 100644 --- a/LICENSE +++ b/LICENSE @@ -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 diff --git a/README.md b/README.md index d457207..e40f8f8 100644 --- a/README.md +++ b/README.md @@ -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 +``` + diff --git a/games.csv b/games.csv index f41aa2b..7e5a56d 100644 --- a/games.csv +++ b/games.csv @@ -1,3 +1,2 @@ #port,name -17500,prout -54915,prout2 +2350-2450,trackmania diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..a7d4cb7 --- /dev/null +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..29f572a --- /dev/null +++ b/go.sum @@ -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= diff --git a/main.go b/main.go index 0e32c25..7eda1fe 100644 --- a/main.go +++ b/main.go @@ -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 { @@ -51,31 +59,43 @@ 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) @@ -83,15 +103,21 @@ func interfaceWorker(device Interface, destinationDevices []Interface, ports *[] 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 { @@ -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) } } } @@ -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 { @@ -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) } @@ -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 {