package main import ( "bytes" "encoding/binary" "flag" "fmt" "github.com/google/gopacket" "github.com/google/gopacket/layers" dhcp "github.com/krolaw/dhcp4" log "github.com/sirupsen/logrus" "io" "net" "os" "proxy/cmd" "proxy/util" "time" ) var OldMAC net.HardwareAddr var NewMAC net.HardwareAddr var OldIP net.IP var NewIP net.IP var VmReader io.Reader var VmWriter io.Writer var NetReader io.Reader var NetWriter io.Writer var Passthrough bool // Start the two plugs and run two concurrent forward methods func main() { // Get command line arguments logLvl := flag.Int("log", 4, "allowed: 5 (debug), 4 (info), 3 (warning), 2 (error), 1 (fatal)") oldIP := flag.String("oldip", "", "IP before change") newIP := flag.String("newip", "10.0.0.15", "IP after change") oldMAC := flag.String("oldmac", "", "MAC before change") newMAC := flag.String("newmac", "", "MAC after change") passthrough := flag.Bool("passthrough", false, "Whether to pass every traffic through") sockMain := flag.String("smain", "/run/vde/sw_main.sock", "Main switch sock path, - for stdin/out") sockProxy := flag.String("sproxy", "/run/vde/sw_proxy1.sock", "Proxy switch sock path") pidFile := flag.String("pidfile", "", "Location to write the pid to") logFile := flag.String("logfile", "", "Location to write output to") flag.Parse() log.SetLevel(log.Level(*logLvl)) OldMAC, _ = net.ParseMAC(*oldMAC) NewMAC = util.GenerateMac(*newMAC) OldIP = net.ParseIP(*oldIP).To4() NewIP = net.ParseIP(*newIP).To4() Passthrough = *passthrough log.SetFormatter(&log.TextFormatter{ DisableTimestamp: true, }) if *logFile != "" { if f, err := os.OpenFile(*logFile, os.O_WRONLY | os.O_CREATE, 0755); err != nil { log.Error("Error opening logFile ", *logFile) } else { log.SetOutput(f) } } util.WritePIDFile(*pidFile) var c1, c2 *cmd.Cmd if *sockMain != "-" { c1, NetReader, NetWriter = cmd.Start(*sockMain) } else { NetReader = os.Stdout NetWriter = os.Stdin } c2, VmReader, VmWriter = cmd.Start(*sockProxy) go pipeForward(cmd.In) go pipeForward(cmd.Out) if *sockMain != "-" { c1.WaitH() } c2.WaitH() } // Reads from an input and writes to and output, // do things to the content in between. // Is meant to be run concurrently with "go pipeForward(...)" func pipeForward(prefix string) { var reader io.Reader var writer io.Writer if prefix == cmd.In { reader = NetReader writer = VmWriter } else { reader = VmReader writer = NetWriter } for { // Read frame length frameLength := make([]byte, 2) if _, err := reader.Read(frameLength); err == io.EOF { log.Fatal(prefix, "Error reading frame length") } // Read actual frame frameBytes := make([]byte, int(binary.BigEndian.Uint16(frameLength))) if _, err := reader.Read(frameBytes); err == io.EOF { log.Fatal(prefix, "Error reading frame data") } // Convert frame to full stack packet packet := gopacket.NewPacket(frameBytes, layers.LayerTypeEthernet, gopacket.Default) isInteresting := false // Debug Help // Handle Ethernet frame frame := packet.Layer(layers.LayerTypeEthernet).(*layers.Ethernet) log.Debug("Start packet") filterMAC(prefix, &frame.DstMAC, &frame.SrcMAC, frame.LayerType()) // Handle IPv4 packet if ipv4layer := packet.Layer(layers.LayerTypeIPv4); ipv4layer != nil { ipv4Packet, _ := ipv4layer.(*layers.IPv4) log.Debug("IP Protocol ", ipv4Packet.Protocol) // Handle DHCPv4 packet (based on IPv4) if dhcpLayer := packet.Layer(layers.LayerTypeDHCPv4); dhcpLayer != nil && !Passthrough { handleDHCP(dhcpLayer.LayerContents(), frame.DstMAC, prefix) continue } filterIP(prefix, &ipv4Packet.DstIP, &ipv4Packet.SrcIP, ipv4Packet.LayerType()) // Handle ICMP packet (based on IPv4) if icmpLayer := packet.Layer(layers.LayerTypeICMPv4); icmpLayer != nil { icmpPacket, _ := icmpLayer.(*layers.ICMPv4) log.Debug(prefix, "ICMP Type ", icmpPacket.TypeCode) } // Handle TCP packet if tcpLayer := packet.Layer(layers.LayerTypeTCP); tcpLayer != nil { tcpPacket, _ := tcpLayer.(*layers.TCP) if err := tcpPacket.SetNetworkLayerForChecksum(ipv4Packet); err != nil { log.Error(prefix, "Error setting network layer for checksum", err) } } } // Drop IPv6 packets if ipv6layer := packet.Layer(layers.LayerTypeIPv6); ipv6layer != nil && !Passthrough { log.Info(prefix, "IPv6 packet dropped") continue } // Handle ARP packet if frame.EthernetType == layers.EthernetTypeARP { arpPacket := packet.Layer(layers.LayerTypeARP).(*layers.ARP) log.Debug(prefix, "ARP Type ", arpPacket.Operation) filterIP(prefix, &arpPacket.DstProtAddress, &arpPacket.SourceProtAddress, arpPacket.LayerType()) filterMAC(prefix, &arpPacket.DstHwAddress, &arpPacket.SourceHwAddress, arpPacket.LayerType()) } log.Debug("End packet") // Forward original frame to other plug if Passthrough { writer.Write(frameLength) writer.Write(frameBytes) continue } // Serialize packet back to binary buf := gopacket.NewSerializeBuffer() opts := gopacket.SerializeOptions{ComputeChecksums: true, FixLengths: true} if err := gopacket.SerializePacket(buf, opts, packet); err != nil { log.Errorf("%s Error serializing packet to send\n%s\nSrc:\t%s\nDst:\t%s\n", prefix, err, frame.SrcMAC, frame.DstMAC) continue } newFrameBytes := buf.Bytes() newFrameLength := make([]byte, 2) 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) } // Forward modified frame to other plug writer.Write(newFrameLength) writer.Write(newFrameBytes) } } // filterIP checks whether an IP target selected from src and dst equals a given value. If yes, it is changed func filterIP(prefix string, dst interface{}, src interface{}, context gopacket.LayerType) { var target interface{} var condVal net.IP var newVal net.IP var which string if prefix == cmd.In { target = dst which = "dst" condVal = NewIP newVal = OldIP } else if prefix == cmd.Out { target = src which = "src" condVal = OldIP newVal = NewIP } ip, isIp := target.(*net.IP) bs, isBs := target.(*[]byte) // If no OldIP is set yet, get it from outgoing src field if OldIP == nil { if prefix == cmd.In { return } else if prefix == cmd.Out { if isIp { if!ip.IsGlobalUnicast() { return } OldIP = *ip } else if isBs { OldIP = *bs } log.Info("OldIP set to ", OldIP) condVal = OldIP } } if isIp && bytes.Equal(*ip, condVal) { *ip = newVal log.Debugf("%s%s %s IP %s changed to %s", prefix, context, which, condVal, newVal) } if isBs && bytes.Equal(*bs, condVal) { *bs = newVal log.Debugf("%s%s %s IP %s changed to %s", prefix, context, which, condVal, newVal) } } // filterMAC checks whether a MAC target selected from src and dst equals a given value. If yes, it is changed func filterMAC(prefix string, dst interface{}, src interface{}, context gopacket.LayerType) { // If no OldMAC is set yet, get it from outgoing src field // Has to be HardwareAddr because this is used for ethernet frames which call this method first if OldMAC == nil { if prefix == cmd.In { return } else if prefix == cmd.Out { OldMAC = *src.(*net.HardwareAddr) log.Info("OldMAC set to ", OldMAC) } } var target interface{} var condVal net.HardwareAddr var newVal net.HardwareAddr var which string if prefix == cmd.In { target = dst which = "dst" condVal = NewMAC newVal = OldMAC } else if prefix == cmd.Out { target = src which = "src" condVal = OldMAC newVal = NewMAC } mac, isMac := target.(*net.HardwareAddr) bs, isBs := target.(*[]byte) if isMac && bytes.Equal(*mac, condVal) { *mac = newVal log.Debugf("%s%s %s MAC %s changed to %s", prefix, context, which, condVal, newVal) } if isBs && bytes.Equal(*bs, condVal) { *bs = newVal log.Debugf("%s%s %s MAC %s changed to %s", prefix, context, which, condVal, newVal) } } func handleDHCP(content []byte, srcMAC net.HardwareAddr, prefix string) { req := dhcp.Packet(content) if req.HLen() > 16 { // Invalid size log.Error(prefix, "Invalid DHCP size") return } options := req.ParseOptions() var reqType dhcp.MessageType if t := options[dhcp.OptionDHCPMessageType]; len(t) != 1 { log.Error(prefix, "Invalid DHCP message type") return } else { reqType = dhcp.MessageType(t[0]) if reqType < dhcp.Discover || reqType > dhcp.Inform { log.Error(prefix, "Invalid DHCP message type: ", reqType) return } } log.Debug(prefix, "DHCP message registered: ", reqType) if prefix == cmd.Out { switch reqType { case dhcp.Discover: if OldIP == nil { log.Fatal(prefix, "DHCPDISCOVER but not previous address is known") } sendDHCPReply(req, dhcp.Offer, OldIP, options, srcMAC) case dhcp.Request: reqIP := net.IP(options[dhcp.OptionRequestedIPAddress]) if reqIP == nil { reqIP = req.CIAddr() } if len(reqIP) != 4 || reqIP.Equal(net.IPv4zero) { log.Error(prefix, "Invalid IP requested in DHCP: ", reqIP) return } sendDHCPReply(req, dhcp.ACK, reqIP, options, srcMAC) default: return } } else { // TODO: talk to dhcp server } } // sendDHCPReply creates a response dhcp packet and sends it func sendDHCPReply(req dhcp.Packet, mt dhcp.MessageType, lease net.IP, reqOpt dhcp.Options, dstMAC net.HardwareAddr) { 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 := dhcp.Options{ dhcp.OptionSubnetMask: []byte{255, 255, 255, 0}, // TODO: subnet mask dhcp.OptionRouter: serverIP, // TODO: Presuming Server is also your router dhcp.OptionDomainNameServer: serverIP, // TODO: Presuming Server is also your DNS server }.SelectOrderOrAll(reqOpt[dhcp.OptionParameterRequestList]) // Creating the full packet layer by layer dhcplayer := dhcp.ReplyPacket(req, mt, serverIP, lease, 24 * time.Hour, opt) buf := gopacket.NewSerializeBuffer() opts := gopacket.SerializeOptions{ ComputeChecksums: true, FixLengths: true, } ipv4 := layers.IPv4{ Version: 4, TTL: 64, Protocol: layers.IPProtocolUDP, SrcIP: serverIP, DstIP: lease, } udp := layers.UDP{ SrcPort: 67, DstPort: 68, } udp.SetNetworkLayerForChecksum(&ipv4) err := gopacket.SerializeLayers(buf, opts, &layers.Ethernet{ SrcMAC: serverMAC, DstMAC: dstMAC, EthernetType: layers.EthernetTypeIPv4, }, &ipv4, &udp, gopacket.Payload(dhcplayer)) if err != nil { return } packetData := buf.Bytes() // Sending layer through VM's pipe packetLength := make([]byte, 2) binary.BigEndian.PutUint16(packetLength, uint16(len(packetData))) util.WriteBinary(fmt.Sprintf("/tmp/dhcp_%d.dat", time.Now().Unix()), packetData) util.WritePcap(fmt.Sprintf("/tmp/dhcp_%d.pcap", time.Now().Unix()), packetData) util.WritePcap(fmt.Sprintf("/tmp/dhcp_%d2.pcap", time.Now().Unix()), packetData[36:]) VmWriter.Write(packetLength) VmWriter.Write(packetData) }