diff --git a/proxy/main.go b/proxy/main.go index c1b968f..f44e4ce 100644 --- a/proxy/main.go +++ b/proxy/main.go @@ -4,7 +4,6 @@ import ( "bytes" "encoding/binary" "flag" - "fmt" "github.com/google/gopacket" "github.com/google/gopacket/layers" "github.com/krolaw/dhcp4" @@ -13,7 +12,8 @@ import ( "net" "os" "proxy/cmd" - "proxy/util" + . "proxy/util" + "strconv" "time" ) @@ -26,6 +26,15 @@ var VmWriter io.Writer var NetReader io.Reader var NetWriter io.Writer var Passthrough bool +var XId []byte +var DHCPIP net.IP +var RouterIP net.IP +var DNSIP net.IP +var DHCPMAC net.HardwareAddr +var DHCPMask []byte +var DHCPState dhcp4.MessageType +var DHCPCandidate net.IP +var UId string // Start the two plugs and run two concurrent forward methods func main() { @@ -43,10 +52,11 @@ func main() { flag.Parse() log.SetLevel(log.Level(*logLvl)) OldMAC, _ = net.ParseMAC(*oldMAC) - NewMAC = util.GenerateMac(*newMAC) + NewMAC = GenerateMac(*newMAC) OldIP = net.ParseIP(*oldIP).To4() NewIP = net.ParseIP(*newIP).To4() Passthrough = *passthrough + UId = GenerateUId(*sockProxy) log.SetFormatter(&log.TextFormatter{ DisableTimestamp: true, }) @@ -57,7 +67,7 @@ func main() { log.SetOutput(f) } } - util.WritePIDFile(*pidFile) + WritePIDFile(*pidFile) var c1, c2 *cmd.Cmd if *sockMain != "-" { c1, NetReader, NetWriter = cmd.Start(*sockMain) @@ -68,6 +78,7 @@ func main() { c2, VmReader, VmWriter = cmd.Start(*sockProxy) go pipeForward(cmd.In) go pipeForward(cmd.Out) + sendDHCPRequest(dhcp4.Discover, net.IPv4zero) if *sockMain != "-" { c1.WaitH() } @@ -103,7 +114,7 @@ func pipeForward(prefix string) { // Convert frame to full stack packet packet := gopacket.NewPacket(frameBytes, layers.LayerTypeEthernet, gopacket.Default) - isInteresting := false // Debug Help + // isInteresting := false // Debug Help // Handle Ethernet frame frame := packet.Layer(layers.LayerTypeEthernet).(*layers.Ethernet) @@ -117,7 +128,7 @@ func pipeForward(prefix string) { // Handle DHCPv4 packet (based on IPv4) if dhcpLayer := packet.Layer(layers.LayerTypeDHCPv4); dhcpLayer != nil && !Passthrough { - handleDHCP(dhcpLayer.LayerContents(), frame.DstMAC, prefix) + handleDHCP(dhcpLayer.LayerContents(), frame.DstMAC, frame.SrcMAC, prefix) continue } @@ -177,11 +188,11 @@ func pipeForward(prefix string) { binary.BigEndian.PutUint16(newFrameLength, uint16(len(newFrameBytes))) // Write interesting things to debug file - if isInteresting { - util.WriteBinary(fmt.Sprintf("/tmp/pck_%di.dat", time.Now().Unix()), frameBytes) - util.WriteBinary(fmt.Sprintf("/tmp/pck_%do.dat", time.Now().Unix()), newFrameBytes) - //util.WritePcapNg("xyz.pcap", packet.Data(), packet.Metadata().CaptureInfo) - } + /*if isInteresting { + WriteBinary(fmt.Sprintf("/tmp/pck_%di.dat", time.Now().Unix()), frameBytes) + WriteBinary(fmt.Sprintf("/tmp/pck_%do.dat", time.Now().Unix()), newFrameBytes) + //WritePcapNg("xyz.pcap", packet.Data(), packet.Metadata().CaptureInfo) + }*/ // Forward modified frame to other plug if _, err := writer.Write(newFrameLength); err != nil { @@ -281,7 +292,7 @@ func filterMAC(prefix string, dst interface{}, src interface{}, context gopacket } } -func handleDHCP(content []byte, srcMAC net.HardwareAddr, prefix string) { +func handleDHCP(content []byte, dstMAC net.HardwareAddr, srcMAC net.HardwareAddr, prefix string) { req := dhcp4.Packet(content) if req.HLen() > 16 { // Invalid size log.Error(prefix, "Invalid DHCP size") @@ -306,7 +317,9 @@ func handleDHCP(content []byte, srcMAC net.HardwareAddr, prefix string) { if OldIP == nil { log.Fatal(prefix, "DHCPDISCOVER but not previous address is known") } - sendDHCPReply(req, dhcp4.Offer, OldIP, options, srcMAC) + sendDHCPReply(req, dhcp4.Offer, OldIP, options, dstMAC) + case dhcp4.Inform: + fallthrough case dhcp4.Request: reqIP := net.IP(options[dhcp4.OptionRequestedIPAddress]) if reqIP == nil { @@ -316,25 +329,63 @@ func handleDHCP(content []byte, srcMAC net.HardwareAddr, prefix string) { log.Error(prefix, "Invalid IP requested in DHCP: ", reqIP) return } - sendDHCPReply(req, dhcp4.ACK, reqIP, options, srcMAC) - default: - return + sendDHCPReply(req, dhcp4.ACK, reqIP, options, dstMAC) } } else { - // TODO: talk to DHCP server + switch reqType { + case dhcp4.Offer: + if DHCPState == dhcp4.Discover { + DHCPMAC = srcMAC + offIP := req.YIAddr() + if len(offIP) != 4 || offIP.Equal(net.IPv4zero) { + log.Error(prefix, "Invalid IP offered in DHCP: ", offIP) + return + } + if dhcpip := options[dhcp4.OptionServerIdentifier]; dhcpip != nil { + DHCPIP = dhcpip + } + if mask := options[dhcp4.OptionSubnetMask]; mask != nil { + DHCPMask = mask + } + if dns := options[dhcp4.OptionDomainNameServer]; dns != nil { + DNSIP = dns + } + if router := options[dhcp4.OptionRouter]; router != nil { + RouterIP = router + } + DHCPCandidate = offIP + sendDHCPRequest(dhcp4.Request, offIP) + } + case dhcp4.ACK: + if DHCPState == dhcp4.Request { + NewIP = DHCPCandidate + DHCPCandidate = nil + DHCPState = 0 + log.Info("DHCP lease accepted: ", NewIP) + } + case dhcp4.NAK: + if DHCPState == dhcp4.Request { + sendDHCPRequest(dhcp4.Discover, net.IPv4zero) + } + } } } // sendDHCPReply creates a response DHCP packet and sends it func sendDHCPReply(req dhcp4.Packet, mt dhcp4.MessageType, lease net.IP, reqOpt dhcp4.Options, dstMAC net.HardwareAddr) { + if DHCPIP == nil || DHCPMAC == nil { + log.Info("DHCP server is not known, discover request from VM discarded") + sendDHCPRequest(dhcp4.Discover, net.IPv4zero) + return + } + log.Info("Sending DHCP response: ", mt, lease) + // Getting the options - serverIP := []byte{10, 0, 0, 1} // TODO: serverIP - serverMAC := util.GenerateMac("") // TODO: server MAC opt := dhcp4.Options{ - dhcp4.OptionSubnetMask: []byte{255, 255, 255, 0}, // TODO: subnet mask - dhcp4.OptionRouter: serverIP, // TODO: Presuming Server is also your router - dhcp4.OptionDomainNameServer: serverIP, // TODO: Presuming Server is also your DNS server + dhcp4.OptionSubnetMask: DHCPMask, + dhcp4.OptionRouter: RouterIP, + dhcp4.OptionDomainNameServer: DNSIP, }.SelectOrderOrAll(reqOpt[dhcp4.OptionParameterRequestList]) // Creating the full packet layer by layer @@ -344,27 +395,27 @@ func sendDHCPReply(req dhcp4.Packet, mt dhcp4.MessageType, lease net.IP, reqOpt FixLengths: true, } eth := layers.Ethernet{ - SrcMAC: serverMAC, + SrcMAC: DHCPMAC, DstMAC: dstMAC, EthernetType: layers.EthernetTypeIPv4, } ipv4 := layers.IPv4{ Version: 4, - TTL: 64, + TTL: 128, Protocol: layers.IPProtocolUDP, - SrcIP: serverIP, + SrcIP: DHCPIP, DstIP: lease, } udp := layers.UDP{ SrcPort: 67, DstPort: 68, } - dhcp := dhcp4.ReplyPacket(req, mt, serverIP, lease, 24*time.Hour, opt) + dhcp := dhcp4.ReplyPacket(req, mt, DHCPIP, lease, 24*time.Hour, opt) if err := udp.SetNetworkLayerForChecksum(&ipv4); err != nil { - log.Error("Error building DHCP response", err) + log.Error("Error building DHCP response:", err) } if err := gopacket.SerializeLayers(buf, opts, ð, &ipv4, &udp, gopacket.Payload(dhcp)); err != nil { - log.Error("Error serializing DHCP response", err) + log.Error("Error serializing DHCP response:", err) } packetData := buf.Bytes() @@ -379,3 +430,69 @@ func sendDHCPReply(req dhcp4.Packet, mt dhcp4.MessageType, lease net.IP, reqOpt log.Error("Error writing DHCP response data", err) } } + +func sendDHCPRequest(mt dhcp4.MessageType, reqIP net.IP) { + log.Info("Sending DHCP request: ", mt) + if mt == dhcp4.Discover { + XId = GenerateXID() + } + DHCPState = mt + + // Creating the full packet layer by layer + buf := gopacket.NewSerializeBuffer() + serializeOpts := gopacket.SerializeOptions{ + ComputeChecksums: true, + FixLengths: true, + } + eth := layers.Ethernet{ + SrcMAC: NewMAC, + DstMAC: If(mt == dhcp4.Discover).MAC([]byte{255,255,255,255,255,255}, DHCPMAC), + EthernetType: layers.EthernetTypeIPv4, + } + ipv4 := layers.IPv4{ + Version: 4, + TTL: 128, + Protocol: layers.IPProtocolUDP, + SrcIP: net.IPv4zero, + DstIP: net.IPv4bcast, + } + udp := layers.UDP{ + SrcPort: 68, + DstPort: 67, + } + dhcpOpts := []dhcp4.Option{ + { + Code: dhcp4.OptionHostName, + Value: []byte("vdeproxy" + UId), + }, + { + Code: dhcp4.OptionParameterRequestList, + Value: []byte{1, 3, 6}, // Subnet Mask, Router, Domain Name Server + }, + } + if mt == dhcp4.Request { + dhcpOpts = append(dhcpOpts, dhcp4.Option{ + Code: dhcp4.OptionRequestedIPAddress, + Value: reqIP.To4(), + }) + } + dhcp := dhcp4.RequestPacket(mt, NewMAC, reqIP, XId, false, dhcpOpts) + if err := udp.SetNetworkLayerForChecksum(&ipv4); err != nil { + log.Error("Error building DHCP request: ", err) + } + if err := gopacket.SerializeLayers(buf, serializeOpts, ð, &ipv4, &udp, gopacket.Payload(dhcp)); err != nil { + log.Error("Error serializing DHCP request: ", err) + } + packetData := buf.Bytes() + WritePcap("/tmp/dhcpreq_" +strconv.FormatInt(time.Now().Unix(), 10)+ ".pcap", packetData) + + // Sending layer through VM's pipe + packetLength := make([]byte, 2) + binary.BigEndian.PutUint16(packetLength, uint16(len(packetData))) + if _, err := NetWriter.Write(packetLength); err != nil { + log.Error("Error writing DHCP response length: ", err) + } + if _, err := NetWriter.Write(packetData); err != nil { + log.Error("Error writing DHCP response data: ", err) + } +} diff --git a/proxy/util/util.go b/proxy/util/util.go index 93a80a1..b4e231a 100644 --- a/proxy/util/util.go +++ b/proxy/util/util.go @@ -2,19 +2,22 @@ package util import ( "crypto/rand" + "encoding/base32" "github.com/google/gopacket" "github.com/google/gopacket/layers" "github.com/google/gopacket/pcapgo" log "github.com/sirupsen/logrus" + "hash/adler32" "io/ioutil" "net" "os" "strconv" + "strings" "time" ) -// WritePcapNg writes the provided data to a given pcap file -func WritePcapNg(file string, data []byte) { +// WritePcap writes the provided data to a given pcap file +func WritePcap(file string, data []byte) { f, err := os.Create(file) if err != nil { log.Errorf("Error writing pcap file %s", file) @@ -36,19 +39,6 @@ func WritePcapNg(file string, data []byte) { err = r.WritePacket(ci, data) } -func WritePcap(file string, data []byte) { - f, _ := os.Create(file) - w := pcapgo.NewWriter(f) - w.WriteFileHeader(65536, layers.LinkTypeEthernet) // new file, must do this. - ci := gopacket.CaptureInfo{ - Timestamp: time.Now(), - CaptureLength: len(data), - Length: len(data), - } - w.WritePacket(ci, data) - f.Close() -} - // WriteBinary writes the provided data to a given binary file func WriteBinary(file string, data []byte) { if err := ioutil.WriteFile(file, data, 0644); err != nil { @@ -82,3 +72,55 @@ func GenerateMac(customMAC string) net.HardwareAddr { mac = append(mac, buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]) return mac } + +func GenerateXID() []byte { + buf := make([]byte, 4) + if _, err := rand.Read(buf); err != nil { + log.Error("Error generating random xid") + return []byte{36, 23, 250, 224} // chosen by fair dice roll + // guaranteed to be random --> https://xkcd.com/221/ + } + return buf +} + +func GenerateUId(path string) string { + adl := adler32.Checksum([]byte(path)) + byt := make([]byte, 4) + for i := uint32(0); i < 4; i++ { + byt[i] = byte((adl >> (8 * i)) & 0xff) + } + b32 := base32.StdEncoding.EncodeToString(byt) + return strings.ReplaceAll(b32, "=", "") +} + +// If is a helper type to form expressive ternary expressions being the +// concatenation of a type conversion and a method call such as: +// +// i := If(cond).Int(a, b) +// +// For details, see https://stackoverflow.com/a/59375088/1705598 +type If bool + +// If returns a if c is true, b otherwise. +func (c If) If(a, b interface{}) interface{} { + if c { + return a + } + return b +} + +// MAC returns a if c is true, b otherwise. +func (c If) MAC(a, b net.HardwareAddr) net.HardwareAddr { + if c { + return a + } + return b +} + +// IP returns a if c is true, b otherwise. +func (c If) IP(a, b net.IP) net.IP { + if c { + return a + } + return b +} \ No newline at end of file