Skip to content

Commit 9c36680

Browse files
authored
Merge pull request #22 from synfinatic/auto-learn
learn client IPs on non-broadcast interfaces
2 parents 939ecfd + 8a785e2 commit 9c36680

File tree

5 files changed

+175
-96
lines changed

5 files changed

+175
-96
lines changed

Makefile

+4-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ GOARCH ?= $(shell uname -m)
44
BUILDINFOSDET ?=
55
UDP_PROXY_2020_ARGS ?=
66

7-
PROJECT_VERSION := 0.0.3
7+
PROJECT_VERSION := 0.0.4
88
DOCKER_REPO := synfinatic
99
PROJECT_NAME := udp-proxy-2020
1010
PROJECT_TAG := $(shell git describe --tags 2>/dev/null $(git rev-list --tags --max-count=1))
@@ -79,21 +79,22 @@ fmt: ## Format Go code
7979
@go fmt cmd
8080

8181
.PHONY: test-fmt
82-
test-fmt: fmt
82+
test-fmt: fmt ## Test to make sure code if formatted correctly
8383
@if test `git diff cmd | wc -l` -gt 0; then \
8484
echo "Code changes detected when running 'go fmt':" ; \
8585
git diff -Xfiles ; \
8686
exit -1 ; \
8787
fi
8888

8989
.PHONY: test-tidy
90-
test-tidy:
90+
test-tidy: ## Test to make sure go.mod is tidy
9191
@go mod tidy
9292
@if test `git diff go.mod | wc -l` -gt 0; then \
9393
echo "Need to run 'go mod tidy' to clean up go.mod" ; \
9494
exit -1 ; \
9595
fi
9696

97+
precheck: test test-fmt test-tidy ## Run all tests that happen in a PR
9798

9899
######################################################################
99100
# Docker targets for testing

README.md

+7
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,10 @@ udp-proxy-2020 is still under heavy development. Run `udp-proxy-2020 --help`
7878
for a current list of command line options. Also, please note on many operating
7979
systems you will need to run it as the `root` user. Linux systems can
8080
optionally grant the `CAP_NET_RAW` capability.
81+
82+
Currently there are only a few flags you probaly need to worry about:
83+
84+
* `--interface` -- specify two or more network interfaces to listen on
85+
* `--port` -- specify one or more UDP ports to monitor
86+
87+
There are other flags of course, run `./udp-proxy-2020 --help` for a full list.

cmd/interfaces.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,9 @@ func initializeInterface(l *Listen) {
5050
}
5151

5252
// set our BPF filter
53-
log.Debugf("%s: applying BPF Filter: %s", l.iname, l.filter)
54-
err = l.handle.SetBPFFilter(l.filter)
53+
bpf_filter := buildBPFFilter(l.ports)
54+
log.Debugf("%s: applying BPF Filter: %s", l.iname, bpf_filter)
55+
err = l.handle.SetBPFFilter(bpf_filter)
5556
if err != nil {
5657
log.Fatalf("%s: %s", l.iname, err)
5758
}

cmd/listen.go

+130-72
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package main
22

33
import (
4+
"encoding/binary"
45
"net"
5-
"strings"
66
"sync"
77
"time"
88

@@ -16,15 +16,16 @@ const SendBufferSize = 100
1616

1717
// Struct containing everything for an interface
1818
type Listen struct {
19-
iname string // interface to use
20-
iface *net.Interface // interface descriptor
21-
filter string // bpf filter string to listen on
22-
ports []int32 // port(s) we listen for packets
23-
ipaddr string // dstip we send packets to
24-
promisc bool // do we enable promisc on this interface?
25-
handle *pcap.Handle
26-
timeout time.Duration
27-
sendpkt chan Send // channel used to recieve packets we need to send
19+
iname string // interface to use
20+
netif *net.Interface // interface descriptor
21+
ports []int32 // port(s) we listen for packets
22+
ipaddr string // dstip we send packets to
23+
promisc bool // do we enable promisc on this interface?
24+
handle *pcap.Handle // gopacket.pcap handle
25+
timeout time.Duration // timeout for loop
26+
clientTTL time.Duration // ttl for client cache
27+
sendpkt chan Send // channel used to recieve packets we need to send
28+
clients map[string]time.Time // keep track of clients for non-promisc interfaces
2829
}
2930

3031
// List of LayerTypes we support in sendPacket()
@@ -34,61 +35,52 @@ var validLinkTypes = []layers.LinkType{
3435
layers.LinkTypeNull,
3536
}
3637

37-
// takes the list of listen or promisc and returns a list of Listen
38-
// which then can be initialized
39-
func processListener(interfaces *[]string, lp []string, bpf_filter string, ports []int32, to time.Duration) []Listen {
40-
var ret = []Listen{}
41-
for _, i := range lp {
42-
s := strings.Split(i, "@")
43-
if len(s) != 2 {
44-
log.Fatalf("%s is invalid. Expected: <interface>@<ipaddr>", i)
45-
}
46-
iname := s[0]
47-
ipaddr := s[1]
48-
49-
iname_prefix := iname + "@"
50-
if stringPrefixInSlice(iname_prefix, *interfaces) {
51-
log.Fatalf("Can't specify the same interface (%s) multiple times", iname)
52-
}
53-
*interfaces = append(*interfaces, iname)
54-
55-
netif, err := net.InterfaceByName(iname)
38+
// Creates a Listen struct for the given interface, promisc mode, udp sniff ports and timeout
39+
func newListener(netif *net.Interface, promisc bool, ports []int32, to time.Duration) Listen {
40+
log.Debugf("%s: ifIndex: %d", netif.Name, netif.Index)
41+
addrs, err := netif.Addrs()
42+
if err != nil {
43+
log.Fatalf("Unable to obtain addresses for %s", netif.Name)
44+
}
45+
var bcastaddr string = ""
46+
// only calc the broadcast address on promiscuous interfaces
47+
// for non-promisc, we use our clients
48+
if !promisc {
49+
for _, addr := range addrs {
50+
log.Debugf("%s network: %s\t\tstring: %s", netif.Name, addr.Network(), addr.String())
5651

57-
if err != nil {
58-
log.Fatalf("Unable to get network index for %s: %s", iname, err)
52+
_, ipNet, err := net.ParseCIDR(addr.String())
53+
if err != nil {
54+
log.Debugf("%s: Unable to parse CIDR: %s (%s)", netif.Name, addr.String(), addr.Network())
55+
continue
56+
}
57+
if ipNet.IP.To4() == nil {
58+
continue // Skip non-IPv4 addresses
59+
}
60+
// calc broadcast
61+
ip := make(net.IP, len(ipNet.IP.To4()))
62+
bcastbin := binary.BigEndian.Uint32(ipNet.IP.To4()) | ^binary.BigEndian.Uint32(net.IP(ipNet.Mask).To4())
63+
binary.BigEndian.PutUint32(ip, bcastbin)
64+
bcastaddr = ip.String()
5965
}
60-
log.Debugf("%s: ifIndex: %d", iname, netif.Index)
61-
62-
// check if interface has broadcast capabilities, when yes promisc = true
63-
hasBroadcast := (netif.Flags & net.FlagBroadcast) != 0
64-
65-
new := Listen{
66-
iname: iname,
67-
iface: netif,
68-
filter: bpf_filter,
69-
ports: ports,
70-
ipaddr: ipaddr,
71-
timeout: to,
72-
promisc: hasBroadcast,
73-
handle: nil,
74-
sendpkt: make(chan Send, SendBufferSize),
66+
// promisc interfaces should have a bcast/ipv4 config
67+
if len(bcastaddr) == 0 && promisc {
68+
log.Fatalf("%s does not have a valid IPv4 configuration", netif.Name)
7569
}
76-
ret = append(ret, new)
7770
}
78-
return ret
79-
}
80-
81-
// takes list of interfaces to listen on, if we should listen promiscuously,
82-
// the BPF filter, list of ports and timeout and returns a list of processListener
83-
func initializeListeners(inames []string, bpf_filter string, ports []int32, timeout time.Duration) []Listen {
84-
// process our promisc and listen interfaces
85-
var interfaces = []string{}
86-
var listeners []Listen
87-
a := processListener(&interfaces, inames, bpf_filter, ports, timeout)
88-
for _, x := range a {
89-
listeners = append(listeners, x)
71+
new := Listen{
72+
iname: netif.Name,
73+
netif: netif,
74+
ports: ports,
75+
ipaddr: bcastaddr,
76+
timeout: to,
77+
promisc: promisc,
78+
handle: nil,
79+
sendpkt: make(chan Send, SendBufferSize),
80+
clients: make(map[string]time.Time),
9081
}
91-
return listeners
82+
log.Debugf("Listen: %v", new)
83+
return new
9284
}
9385

9486
// Our goroutine for processing packets
@@ -108,7 +100,7 @@ func (l *Listen) handlePackets(s *SendPktFeed, wg *sync.WaitGroup) {
108100
for {
109101
select {
110102
case s := <-l.sendpkt: // packet arrived from another interface
111-
l.sendPacket(s)
103+
l.sendPackets(s)
112104
case packet := <-packets: // packet arrived on this interfaces
113105
// is it legit?
114106
if packet.NetworkLayer() == nil || packet.TransportLayer() == nil || packet.TransportLayer().LayerType() != layers.LayerTypeUDP {
@@ -118,16 +110,28 @@ func (l *Listen) handlePackets(s *SendPktFeed, wg *sync.WaitGroup) {
118110
log.Errorf("%s: Unable to decode: %s", l.iname, errx.Error())
119111
}
120112

113+
// if our interface is non-promisc, learn the client IP
114+
if l.promisc {
115+
l.learnClientIP(packet)
116+
}
117+
121118
log.Debugf("%s: received packet and fowarding onto other interfaces", l.iname)
122119
s.Send(packet, l.iname, l.handle.LinkType())
123120
case <-ticker: // our timer
124121
log.Debugf("handlePackets(%s) ticker", l.iname)
122+
// clean client cache
123+
for k, v := range l.clients {
124+
if v.Before(time.Now()) {
125+
log.Debugf("%s removing %s after %dsec", l.iname, k, l.clientTTL)
126+
delete(l.clients, k)
127+
}
128+
}
125129
}
126130
}
127131
}
128132

129133
// Does the heavy lifting of editing & sending the packet onwards
130-
func (l *Listen) sendPacket(sndpkt Send) {
134+
func (l *Listen) sendPackets(sndpkt Send) {
131135
var eth layers.Ethernet
132136
var loop layers.Loopback // BSD NULL/Loopback used for OpenVPN tunnels/etc
133137
var ip4 layers.IPv4 // we only support v4
@@ -146,7 +150,6 @@ func (l *Listen) sendPacket(sndpkt Send) {
146150
parser = gopacket.NewDecodingLayerParser(layers.LayerTypeEthernet, &eth, &ip4, &udp, &payload)
147151
default:
148152
log.Fatalf("Unsupported source linktype: 0x%02x", sndpkt.linkType)
149-
return
150153
}
151154

152155
// try decoding our packet
@@ -156,7 +159,7 @@ func (l *Listen) sendPacket(sndpkt Send) {
156159
return
157160
}
158161

159-
// packet was decoded
162+
// was packet decoded? In theory, this should never happen because our BPF filter...
160163
found_udp := false
161164
found_ipv4 := false
162165
for _, layerType := range decoded {
@@ -172,6 +175,27 @@ func (l *Listen) sendPacket(sndpkt Send) {
172175
return
173176
}
174177

178+
if !l.promisc {
179+
// send one packet to broadcast IP
180+
dstip := net.ParseIP(l.ipaddr).To4()
181+
if err, bytes := l.sendPacket(dstip, eth, loop, ip4, udp, payload); err != nil {
182+
log.Warnf("Unable to send %d bytes from %s out %s: %s",
183+
bytes, sndpkt.srcif, l.iname, err)
184+
}
185+
} else {
186+
// sent packet to every client
187+
for ip, _ := range l.clients {
188+
dstip := net.ParseIP(ip).To4()
189+
if err, bytes := l.sendPacket(dstip, eth, loop, ip4, udp, payload); err != nil {
190+
log.Warnf("Unable to send %d bytes from %s out %s: %s",
191+
bytes, sndpkt.srcif, l.iname, err)
192+
}
193+
}
194+
}
195+
}
196+
197+
func (l *Listen) sendPacket(dstip net.IP, eth layers.Ethernet, loop layers.Loopback,
198+
ip4 layers.IPv4, udp layers.UDP, payload gopacket.Payload) (error, int) {
175199
// Build our packet to send
176200
buffer := gopacket.NewSerializeBuffer()
177201
csum_opts := gopacket.SerializeOptions{
@@ -215,15 +239,15 @@ func (l *Listen) sendPacket(sndpkt Send) {
215239
Protocol: ip4.Protocol,
216240
Checksum: 0, // reset to calc checksums
217241
SrcIP: ip4.SrcIP,
218-
DstIP: net.ParseIP(l.ipaddr).To4(),
242+
DstIP: dstip,
219243
Options: ip4.Options,
220244
}
221245
if err := new_ip4.SerializeTo(buffer, csum_opts); err != nil {
222246
log.Fatalf("can't serialize IP header: %v", new_ip4)
223247
}
224248

225249
// Loopback or Ethernet
226-
if (l.iface.Flags & net.FlagLoopback) > 0 {
250+
if (l.netif.Flags & net.FlagLoopback) > 0 {
227251
loop := layers.Loopback{
228252
Family: layers.ProtocolFamilyIPv4,
229253
}
@@ -235,7 +259,7 @@ func (l *Listen) sendPacket(sndpkt Send) {
235259
new_eth := layers.Ethernet{
236260
BaseLayer: layers.BaseLayer{},
237261
DstMAC: net.HardwareAddr{0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
238-
SrcMAC: l.iface.HardwareAddr,
262+
SrcMAC: l.netif.HardwareAddr,
239263
EthernetType: layers.EthernetTypeIPv4,
240264
}
241265
if err := new_eth.SerializeTo(buffer, opts); err != nil {
@@ -244,11 +268,45 @@ func (l *Listen) sendPacket(sndpkt Send) {
244268
}
245269

246270
outgoingPacket := buffer.Bytes()
247-
log.Debugf("%s: packet len: %d: %v", l.iname, len(outgoingPacket), outgoingPacket)
248-
err := l.handle.WritePacketData(outgoingPacket)
249-
if err != nil {
250-
log.Warnf("Unable to send %d bytes from %s out %s: %s",
251-
len(outgoingPacket), sndpkt.srcif, l.iname, err)
271+
log.Debugf("%s => %s: packet len: %d: %v", l.iname, dstip.String(), len(outgoingPacket), outgoingPacket)
272+
return l.handle.WritePacketData(outgoingPacket), len(outgoingPacket)
273+
}
274+
275+
func (l *Listen) learnClientIP(packet gopacket.Packet) {
276+
var eth layers.Ethernet
277+
var loop layers.Loopback
278+
var ip4 layers.IPv4
279+
var udp layers.UDP
280+
var payload gopacket.Payload
281+
var parser *gopacket.DecodingLayerParser
282+
283+
switch l.handle.LinkType() {
284+
case layers.LinkTypeNull:
285+
parser = gopacket.NewDecodingLayerParser(layers.LayerTypeLoopback, &loop, &ip4, &udp, &payload)
286+
case layers.LinkTypeLoop:
287+
parser = gopacket.NewDecodingLayerParser(layers.LayerTypeLoopback, &loop, &ip4, &udp, &payload)
288+
case layers.LinkTypeEthernet:
289+
parser = gopacket.NewDecodingLayerParser(layers.LayerTypeEthernet, &eth, &ip4, &udp, &payload)
290+
default:
291+
log.Fatalf("Unsupported source linktype: 0x%02x", l.handle.LinkType())
292+
}
293+
294+
decoded := []gopacket.LayerType{}
295+
if err := parser.DecodeLayers(packet.Data(), &decoded); err != nil {
296+
log.Debugf("Unable to decoded client IP on %s: %s", l.iname, err)
297+
}
298+
299+
found_ipv4 := false
300+
for _, layerType := range decoded {
301+
switch layerType {
302+
case layers.LayerTypeIPv4:
303+
// found our v4 header
304+
found_ipv4 = true
305+
}
306+
}
307+
308+
if found_ipv4 {
309+
l.clients[ip4.SrcIP.String()] = time.Now().Add(l.clientTTL)
252310
}
253311
}
254312

0 commit comments

Comments
 (0)