Skip to content

Commit

Permalink
Rewrite source ip per vlan to prevent out of subnet traffic
Browse files Browse the repository at this point in the history
Still falls back to old behaviour if config is not found for the vlan. Thx to @jorisjean for the inspiration.
Rewrote SendPacket with SSDP support in mind.
Fix a casesensitive mac address issue
  • Loading branch information
nberlee committed Mar 18, 2022
1 parent 2e1f723 commit bdd1ebb
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 49 deletions.
47 changes: 40 additions & 7 deletions config.go
Original file line number Diff line number Diff line change
@@ -1,33 +1,43 @@
package main

import (
"fmt"
"io/ioutil"
"net"
"strconv"
"strings"

"github.com/pelletier/go-toml"
)

type macAddress string

type brconfig struct {
NetInterface string `toml:"net_interface"`
Devices map[macAddress]bonjourDevice `toml:"devices"`
type config struct {
NetInterface string `toml:"net_interface"`
Devices map[macAddress]multicastDevice `toml:"devices"`
VlanIPSource map[vlanID]vlanIpSource `toml:"vlan"`
}

type bonjourDevice struct {
type multicastDevice struct {
OriginPool uint16 `toml:"origin_pool"`
SharedPools []uint16 `toml:"shared_pools"`
}

func readConfig(path string) (cfg brconfig, err error) {
type vlanID string
type vlanIpSource struct {
IpSource net.IP `toml:"ip_source"`
}

func readConfig(path string) (cfg config, err error) {
content, err := ioutil.ReadFile(path)
if err != nil {
return brconfig{}, err
return config{}, err
}
err = toml.Unmarshal(content, &cfg)
return cfg, err
}

func mapByPool(devices map[macAddress]bonjourDevice) map[uint16]([]uint16) {
func mapByPool(devices map[macAddress]multicastDevice) map[uint16]([]uint16) {
seen := make(map[uint16]map[uint16]bool)
poolsMap := make(map[uint16]([]uint16))
for _, device := range devices {
Expand All @@ -43,3 +53,26 @@ func mapByPool(devices map[macAddress]bonjourDevice) map[uint16]([]uint16) {
}
return poolsMap
}

func mapIpSourceByVlan(vlanipsource map[vlanID]vlanIpSource) map[uint16](net.IP) {
vlanMap := make(map[uint16](net.IP))
for vlan, value := range vlanipsource {
vlanID, err := strconv.Atoi(string(vlan))
if err != nil {
fmt.Printf("Cannot decode %s to vlanID", vlan)
continue
}
vlanMap[uint16(vlanID)] = value.IpSource
}
return vlanMap
}

func mapLowerCaseMac(devices map[macAddress]multicastDevice) map[macAddress]multicastDevice {
newDevices := make(map[macAddress]multicastDevice)
for mac, device := range devices {
lowerCaseMac := strings.ToLower(string(mac))

newDevices[macAddress(lowerCaseMac)] = device
}
return newDevices
}
11 changes: 11 additions & 0 deletions config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,14 @@ net_interface = "wls1" # Put here the network interface you want to use.
description = "Test Spotify Air"
origin_pool = 1547
shared_pools = [1078, 2483, 3133]

[vlan]

[vlan.1078]
ip_source = "192.168.1.253"

[vlan.1234]
ip_source = "192.168.2.253"

[vlan.1547]
ip_source = "192.168.3.253"
6 changes: 4 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
module github.com/Gandem/bonjour-reflector
module github.com/nberlee/bonjour-reflector

go 1.15
go 1.17

require (
github.com/charithe/timedbuf v0.0.0-20160717160936-e4a8ee453eb4
github.com/google/gopacket v1.1.20-0.20210429153827-3eaba0894325
github.com/oaStuff/timedMap v0.0.0-20171127021412-9b26c8f4a1a6 // indirect
github.com/pelletier/go-toml v1.9.3
)
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
github.com/charithe/timedbuf v0.0.0-20160717160936-e4a8ee453eb4 h1:kR+AroUEp9FGkLiDwitQTESnrovc4++VRw693df+O2Y=
github.com/charithe/timedbuf v0.0.0-20160717160936-e4a8ee453eb4/go.mod h1:9ev5LJuOzZvgdjc1JpUAC5zK1a8Bg02xf29HWlb5QgQ=
github.com/google/gopacket v1.1.20-0.20210429153827-3eaba0894325 h1:YmIcZ5Var3BAQ64AW98Iiys5Ih4fiU0xK41+8isC5Ec=
github.com/google/gopacket v1.1.20-0.20210429153827-3eaba0894325/go.mod h1:riddUzxTSBpJXk3qBHtYr4qOhFhT6k/1c0E3qkQjQpA=
github.com/oaStuff/timedMap v0.0.0-20171127021412-9b26c8f4a1a6 h1:LXLcwOuNMKism0qpzdtd95nAQN166sO5nptNkvWXDaQ=
github.com/oaStuff/timedMap v0.0.0-20171127021412-9b26c8f4a1a6/go.mod h1:r8nRcFjSRvj9WdcJ6t0Izw85+W64lJdIdtLLuWl64zo=
github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ=
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
Expand Down
49 changes: 40 additions & 9 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ func main() {
log.Fatalf("Could not read configuration: %v", err)
}
poolsMap := mapByPool(cfg.Devices)
vlanIPMap := mapIpSourceByVlan(cfg.VlanIPSource)
allowedMacsMap := mapLowerCaseMac(cfg.Devices)

// Get a handle on the network interface
rawTraffic, err := pcap.OpenLive(cfg.NetInterface, 65536, true, time.Second)
Expand All @@ -41,11 +43,16 @@ func main() {
if err != nil {
log.Fatal(err)
}
brMACAddress := intf.HardwareAddr
srcMACAddress := intf.HardwareAddr
processBonjourPackets(rawTraffic, srcMACAddress, poolsMap, vlanIPMap, allowedMacsMap)

// Filter tagged bonjour traffic
filterTemplate := "not (ether src %s) and vlan and dst net (224.0.0.251 or ff02::fb) and udp dst port 5353"
err = rawTraffic.SetBPFFilter(fmt.Sprintf(filterTemplate, brMACAddress))
}

func processBonjourPackets(rawTraffic *pcap.Handle, srcMACAddress net.HardwareAddr, poolsMap map[uint16][]uint16, vlanIPMap map[uint16]net.IP, allowedMacsMap map[macAddress]multicastDevice) {
var dstMacAddress net.HardwareAddr

filterTemplate := "not (ether src %s) and vlan and (dst net (224.0.0.251 or ff02::fb) and udp dst port 5353)"
err := rawTraffic.SetBPFFilter(fmt.Sprintf(filterTemplate, srcMACAddress))
if err != nil {
log.Fatalf("Could not apply filter on network interface: %v", err)
}
Expand All @@ -55,9 +62,18 @@ func main() {
source := gopacket.NewPacketSource(rawTraffic, decoder)
bonjourPackets := parsePacketsLazily(source)

// Process Bonjours packets
for bonjourPacket := range bonjourPackets {
fmt.Println(bonjourPacket.packet.String())
fmt.Printf("Bonjour packet received:\n%s\n", bonjourPacket.packet.String())

// Network devices may set dstMAC to the local MAC address
// Rewrite dstMAC to ensure that it is set to the appropriate multicast MAC address
if bonjourPacket.isIPv6 {
dstMacAddress = net.HardwareAddr{0x33, 0x33, 0x00, 0x00, 0x00, 0xFB}
} else {
dstMacAddress = net.HardwareAddr{0x01, 0x00, 0x5E, 0x00, 0x00, 0xFB}
}

var srcIP net.IP

// Forward the mDNS query or response to appropriate VLANs
if bonjourPacket.isDNSQuery {
Expand All @@ -66,15 +82,30 @@ func main() {
continue
}
for _, tag := range tags {
sendBonjourPacket(rawTraffic, &bonjourPacket, tag, brMACAddress)
if !bonjourPacket.isIPv6 {
srcIP, ok = vlanIPMap[tag]
if !ok {
srcIP = nil
}
}

sendPacket(rawTraffic, &bonjourPacket, tag, srcMACAddress, dstMacAddress, srcIP, nil)
}
} else {
device, ok := cfg.Devices[macAddress(bonjourPacket.srcMAC.String())]

device, ok := allowedMacsMap[macAddress(bonjourPacket.srcMAC.String())]
if !ok {
continue
}
for _, tag := range device.SharedPools {
sendBonjourPacket(rawTraffic, &bonjourPacket, tag, brMACAddress)
if !bonjourPacket.isIPv6 {
srcIP, ok = vlanIPMap[tag]
if !ok {
srcIP = nil
}
}

sendPacket(rawTraffic, &bonjourPacket, tag, srcMACAddress, dstMacAddress, srcIP, nil)
}
}
}
Expand Down
104 changes: 73 additions & 31 deletions packet.go
Original file line number Diff line number Diff line change
@@ -1,28 +1,35 @@
package main

import (
"fmt"
"net"
"strings"

"github.com/google/gopacket"
"github.com/google/gopacket/layers"
)

type bonjourPacket struct {
packet gopacket.Packet
srcMAC *net.HardwareAddr
dstMAC *net.HardwareAddr
isIPv6 bool
vlanTag *uint16
isDNSQuery bool
type multicastPacket struct {
packet gopacket.Packet
srcMAC *net.HardwareAddr
dstMAC *net.HardwareAddr
srcIP *net.IP
dstIP *net.IP
srcPort *layers.UDPPort
dstPort *layers.UDPPort
isIPv6 bool
vlanTag *uint16
isDNSQuery bool
isSSDPQuery bool
}

func parsePacketsLazily(source *gopacket.PacketSource) chan bonjourPacket {
func parsePacketsLazily(source *gopacket.PacketSource) chan multicastPacket {
// Process packets, and forward Bonjour traffic to the returned channel

// Set decoding to Lazy
source.DecodeOptions = gopacket.DecodeOptions{Lazy: true}

packetChan := make(chan bonjourPacket, 100)
packetChan := make(chan multicastPacket, 100)

go func() {
for packet := range source.Packets() {
Expand All @@ -32,21 +39,28 @@ func parsePacketsLazily(source *gopacket.PacketSource) chan bonjourPacket {
srcMAC, dstMAC := parseEthernetLayer(packet)

// Check IP protocol version
isIPv6 := parseIPLayer(packet)
isIPv6, srcIP, dstIP := parseIPLayer(packet)

// Get UDP payload
payload := parseUDPLayer(packet)
payload, srcPort, dstPort := parseUDPLayer(packet)

isDNSQuery := parseDNSPayload(payload)

isSSDPQuery := parseSSDPPayload(payload)

// Pass on the packet for its next adventure
packetChan <- bonjourPacket{
packet: packet,
vlanTag: tag,
srcMAC: srcMAC,
dstMAC: dstMAC,
isIPv6: isIPv6,
isDNSQuery: isDNSQuery,
packetChan <- multicastPacket{
packet: packet,
vlanTag: tag,
srcMAC: srcMAC,
dstMAC: dstMAC,
srcIP: srcIP,
dstIP: dstIP,
srcPort: srcPort,
dstPort: dstPort,
isIPv6: isIPv6,
isDNSQuery: isDNSQuery,
isSSDPQuery: isSSDPQuery,
}
}
}()
Expand All @@ -69,19 +83,25 @@ func parseVLANTag(packet gopacket.Packet) (tag *uint16) {
return
}

func parseIPLayer(packet gopacket.Packet) (isIPv6 bool) {
func parseIPLayer(packet gopacket.Packet) (isIPv6 bool, srcIP *net.IP, dstIP *net.IP) {
if parsedIP := packet.Layer(layers.LayerTypeIPv4); parsedIP != nil {
isIPv6 = false
srcIP = &parsedIP.(*layers.IPv4).SrcIP
dstIP = &parsedIP.(*layers.IPv4).DstIP
}
if parsedIP := packet.Layer(layers.LayerTypeIPv6); parsedIP != nil {
isIPv6 = true
srcIP = &parsedIP.(*layers.IPv6).SrcIP
dstIP = &parsedIP.(*layers.IPv6).DstIP
}
return
}

func parseUDPLayer(packet gopacket.Packet) (payload []byte) {
func parseUDPLayer(packet gopacket.Packet) (payload []byte, srcPort *layers.UDPPort, dstPort *layers.UDPPort) {
if parsedUDP := packet.Layer(layers.LayerTypeUDP); parsedUDP != nil {
payload = parsedUDP.(*layers.UDP).Payload
srcPort = &parsedUDP.(*layers.UDP).SrcPort
dstPort = &parsedUDP.(*layers.UDP).DstPort
}
return
}
Expand All @@ -94,23 +114,45 @@ func parseDNSPayload(payload []byte) (isDNSQuery bool) {
return
}

func parseSSDPPayload(payload []byte) (isSSDPQuery bool) {
packet := gopacket.NewPacket(payload, layers.LayerTypeDNS, gopacket.Default)
if ssdp := packet.ApplicationLayer(); ssdp != ssdp {
isSSDPQuery = strings.HasPrefix(string(ssdp.Payload()), "M-SEARCH ")
}
return
}

type packetWriter interface {
WritePacketData([]byte) error
}

func sendBonjourPacket(handle packetWriter, bonjourPacket *bonjourPacket, tag uint16, brMACAddress net.HardwareAddr) {
*bonjourPacket.vlanTag = tag
*bonjourPacket.srcMAC = brMACAddress
func sendPacket(handle packetWriter, packet *multicastPacket, tag uint16, srcMACAddress net.HardwareAddr, dstMacAddress net.HardwareAddr, srcIP net.IP, dstIP net.IP) {
*packet.vlanTag = tag
*packet.srcMAC = srcMACAddress
*packet.dstMAC = dstMacAddress

buf := gopacket.NewSerializeBuffer()
serializeOptions := gopacket.SerializeOptions{}

// Network devices may set dstMAC to the local MAC address
// Rewrite dstMAC to ensure that it is set to the appropriate multicast MAC address
if bonjourPacket.isIPv6 {
*bonjourPacket.dstMAC = net.HardwareAddr{0x33, 0x33, 0x00, 0x00, 0x00, 0xFB}
} else {
*bonjourPacket.dstMAC = net.HardwareAddr{0x01, 0x00, 0x5E, 0x00, 0x00, 0xFB}
if !packet.isIPv6 && (srcIP != nil || dstIP != nil) {
serializeOptions = gopacket.SerializeOptions{ComputeChecksums: true}

if srcIP != nil {
*packet.srcIP = srcIP
}
if dstIP != nil {
*packet.dstIP = dstIP
}
// We recalculate the checksum since the IP was modified
if parsedIP := packet.packet.Layer(layers.LayerTypeIPv4); parsedIP != nil {
if parsedUDP := packet.packet.Layer(layers.LayerTypeUDP); parsedUDP != nil {
parsedUDP.(*layers.UDP).SetNetworkLayerForChecksum(parsedIP.(*layers.IPv4))
}
}
}

buf := gopacket.NewSerializeBuffer()
gopacket.SerializePacket(buf, gopacket.SerializeOptions{}, bonjourPacket.packet)
gopacket.SerializePacket(buf, serializeOptions, packet.packet)
handle.WritePacketData(buf.Bytes())

fmt.Printf("Packet sent:\n%s\n", packet.packet.String())
}

0 comments on commit bdd1ebb

Please sign in to comment.