mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-05 19:21:46 +00:00
261 lines
7.8 KiB
Go
261 lines
7.8 KiB
Go
// Copyright 2012 Google, Inc. All rights reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the LICENSE file in the root of the source
|
|
// tree.
|
|
|
|
// synscan implements a TCP syn scanner on top of pcap.
|
|
// It's more complicated than arpscan, since it has to handle sending packets
|
|
// outside the local network, requiring some routing and ARP work.
|
|
//
|
|
// Since this is just an example program, it aims for simplicity over
|
|
// performance. It doesn't handle sending packets very quickly, it scans IPs
|
|
// serially instead of in parallel, and uses gopacket.Packet instead of
|
|
// gopacket.DecodingLayerParser for packet processing. We also make use of very
|
|
// simple timeout logic with time.Since.
|
|
//
|
|
// Making it blazingly fast is left as an exercise to the reader.
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"time"
|
|
|
|
"github.com/google/gopacket"
|
|
"github.com/google/gopacket/examples/util"
|
|
"github.com/google/gopacket/layers"
|
|
"github.com/google/gopacket/pcap"
|
|
"github.com/google/gopacket/routing"
|
|
)
|
|
|
|
// scanner handles scanning a single IP address.
|
|
type scanner struct {
|
|
// iface is the interface to send packets on.
|
|
iface *net.Interface
|
|
// destination, gateway (if applicable), and soruce IP addresses to use.
|
|
dst, gw, src net.IP
|
|
|
|
handle *pcap.Handle
|
|
|
|
// opts and buf allow us to easily serialize packets in the send()
|
|
// method.
|
|
opts gopacket.SerializeOptions
|
|
buf gopacket.SerializeBuffer
|
|
}
|
|
|
|
// newScanner creates a new scanner for a given destination IP address, using
|
|
// router to determine how to route packets to that IP.
|
|
func newScanner(ip net.IP, router routing.Router) (*scanner, error) {
|
|
s := &scanner{
|
|
dst: ip,
|
|
opts: gopacket.SerializeOptions{
|
|
FixLengths: true,
|
|
ComputeChecksums: true,
|
|
},
|
|
buf: gopacket.NewSerializeBuffer(),
|
|
}
|
|
// Figure out the route to the IP.
|
|
iface, gw, src, err := router.Route(ip)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
log.Printf("scanning ip %v with interface %v, gateway %v, src %v", ip, iface.Name, gw, src)
|
|
s.gw, s.src, s.iface = gw, src, iface
|
|
|
|
// Open the handle for reading/writing.
|
|
// Note we could very easily add some BPF filtering here to greatly
|
|
// decrease the number of packets we have to look at when getting back
|
|
// scan results.
|
|
handle, err := pcap.OpenLive(iface.Name, 65536, true, pcap.BlockForever)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
s.handle = handle
|
|
return s, nil
|
|
}
|
|
|
|
// close cleans up the handle.
|
|
func (s *scanner) close() {
|
|
s.handle.Close()
|
|
}
|
|
|
|
// getHwAddr is a hacky but effective way to get the destination hardware
|
|
// address for our packets. It does an ARP request for our gateway (if there is
|
|
// one) or destination IP (if no gateway is necessary), then waits for an ARP
|
|
// reply. This is pretty slow right now, since it blocks on the ARP
|
|
// request/reply.
|
|
func (s *scanner) getHwAddr() (net.HardwareAddr, error) {
|
|
start := time.Now()
|
|
arpDst := s.dst
|
|
if s.gw != nil {
|
|
arpDst = s.gw
|
|
}
|
|
// Prepare the layers to send for an ARP request.
|
|
eth := layers.Ethernet{
|
|
SrcMAC: s.iface.HardwareAddr,
|
|
DstMAC: net.HardwareAddr{0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
|
EthernetType: layers.EthernetTypeARP,
|
|
}
|
|
arp := layers.ARP{
|
|
AddrType: layers.LinkTypeEthernet,
|
|
Protocol: layers.EthernetTypeIPv4,
|
|
HwAddressSize: 6,
|
|
ProtAddressSize: 4,
|
|
Operation: layers.ARPRequest,
|
|
SourceHwAddress: []byte(s.iface.HardwareAddr),
|
|
SourceProtAddress: []byte(s.src),
|
|
DstHwAddress: []byte{0, 0, 0, 0, 0, 0},
|
|
DstProtAddress: []byte(arpDst),
|
|
}
|
|
// Send a single ARP request packet (we never retry a send, since this
|
|
// is just an example ;)
|
|
if err := s.send(ð, &arp); err != nil {
|
|
return nil, err
|
|
}
|
|
// Wait 3 seconds for an ARP reply.
|
|
for {
|
|
if time.Since(start) > time.Second*3 {
|
|
return nil, fmt.Errorf("timeout getting ARP reply")
|
|
}
|
|
data, _, err := s.handle.ReadPacketData()
|
|
if err == pcap.NextErrorTimeoutExpired {
|
|
continue
|
|
} else if err != nil {
|
|
return nil, err
|
|
}
|
|
packet := gopacket.NewPacket(data, layers.LayerTypeEthernet, gopacket.NoCopy)
|
|
if arpLayer := packet.Layer(layers.LayerTypeARP); arpLayer != nil {
|
|
arp := arpLayer.(*layers.ARP)
|
|
if bytes.Equal(arp.SourceProtAddress, arpDst) {
|
|
return net.HardwareAddr(arp.SourceHwAddress), nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// scan scans the dst IP address of this scanner.
|
|
func (s *scanner) scan() error {
|
|
// First off, get the MAC address we should be sending packets to.
|
|
hwaddr, err := s.getHwAddr()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Construct all the network layers we need.
|
|
eth := layers.Ethernet{
|
|
SrcMAC: s.iface.HardwareAddr,
|
|
DstMAC: hwaddr,
|
|
EthernetType: layers.EthernetTypeIPv4,
|
|
}
|
|
ip4 := layers.IPv4{
|
|
SrcIP: s.src,
|
|
DstIP: s.dst,
|
|
Version: 4,
|
|
TTL: 64,
|
|
Protocol: layers.IPProtocolTCP,
|
|
}
|
|
tcp := layers.TCP{
|
|
SrcPort: 54321,
|
|
DstPort: 0, // will be incremented during the scan
|
|
SYN: true,
|
|
}
|
|
tcp.SetNetworkLayerForChecksum(&ip4)
|
|
|
|
// Create the flow we expect returning packets to have, so we can check
|
|
// against it and discard useless packets.
|
|
ipFlow := gopacket.NewFlow(layers.EndpointIPv4, s.dst, s.src)
|
|
start := time.Now()
|
|
for {
|
|
// Send one packet per loop iteration until we've sent packets
|
|
// to all of ports [1, 65535].
|
|
if tcp.DstPort < 65535 {
|
|
start = time.Now()
|
|
tcp.DstPort++
|
|
if err := s.send(ð, &ip4, &tcp); err != nil {
|
|
log.Printf("error sending to port %v: %v", tcp.DstPort, err)
|
|
}
|
|
}
|
|
// Time out 5 seconds after the last packet we sent.
|
|
if time.Since(start) > time.Second*5 {
|
|
log.Printf("timed out for %v, assuming we've seen all we can", s.dst)
|
|
return nil
|
|
}
|
|
|
|
// Read in the next packet.
|
|
data, _, err := s.handle.ReadPacketData()
|
|
if err == pcap.NextErrorTimeoutExpired {
|
|
continue
|
|
} else if err != nil {
|
|
log.Printf("error reading packet: %v", err)
|
|
continue
|
|
}
|
|
|
|
// Parse the packet. We'd use DecodingLayerParser here if we
|
|
// wanted to be really fast.
|
|
packet := gopacket.NewPacket(data, layers.LayerTypeEthernet, gopacket.NoCopy)
|
|
|
|
// Find the packets we care about, and print out logging
|
|
// information about them. All others are ignored.
|
|
if net := packet.NetworkLayer(); net == nil {
|
|
// log.Printf("packet has no network layer")
|
|
} else if net.NetworkFlow() != ipFlow {
|
|
// log.Printf("packet does not match our ip src/dst")
|
|
} else if tcpLayer := packet.Layer(layers.LayerTypeTCP); tcpLayer == nil {
|
|
// log.Printf("packet has not tcp layer")
|
|
} else if tcp, ok := tcpLayer.(*layers.TCP); !ok {
|
|
// We panic here because this is guaranteed to never
|
|
// happen.
|
|
panic("tcp layer is not tcp layer :-/")
|
|
} else if tcp.DstPort != 54321 {
|
|
// log.Printf("dst port %v does not match", tcp.DstPort)
|
|
} else if tcp.RST {
|
|
log.Printf(" port %v closed", tcp.SrcPort)
|
|
} else if tcp.SYN && tcp.ACK {
|
|
log.Printf(" port %v open", tcp.SrcPort)
|
|
} else {
|
|
// log.Printf("ignoring useless packet")
|
|
}
|
|
}
|
|
}
|
|
|
|
// send sends the given layers as a single packet on the network.
|
|
func (s *scanner) send(l ...gopacket.SerializableLayer) error {
|
|
if err := gopacket.SerializeLayers(s.buf, s.opts, l...); err != nil {
|
|
return err
|
|
}
|
|
return s.handle.WritePacketData(s.buf.Bytes())
|
|
}
|
|
|
|
func main() {
|
|
defer util.Run()()
|
|
router, err := routing.New()
|
|
if err != nil {
|
|
log.Fatal("routing error:", err)
|
|
}
|
|
for _, arg := range flag.Args() {
|
|
var ip net.IP
|
|
if ip = net.ParseIP(arg); ip == nil {
|
|
log.Printf("non-ip target: %q", arg)
|
|
continue
|
|
} else if ip = ip.To4(); ip == nil {
|
|
log.Printf("non-ipv4 target: %q", arg)
|
|
continue
|
|
}
|
|
// Note: newScanner creates and closes a pcap Handle once for
|
|
// every scan target. We could do much better, were this not an
|
|
// example ;)
|
|
s, err := newScanner(ip, router)
|
|
if err != nil {
|
|
log.Printf("unable to create scanner for %v: %v", ip, err)
|
|
continue
|
|
}
|
|
if err := s.scan(); err != nil {
|
|
log.Printf("unable to scan %v: %v", ip, err)
|
|
}
|
|
s.close()
|
|
}
|
|
}
|