package main
import (
"bytes"
"encoding/binary"
"flag"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/krolaw/dhcp4"
log "github.com/sirupsen/logrus"
"io"
"net"
"os"
"proxy/cmd"
. "proxy/util"
"strconv"
"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
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 ( ) {
// 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 = GenerateMac ( * newMAC )
OldIP = net . ParseIP ( * oldIP ) . To4 ( )
NewIP = net . ParseIP ( * newIP ) . To4 ( )
Passthrough = * passthrough
UId = GenerateUId ( * sockProxy )
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 )
}
}
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 )
sendDHCPRequest ( dhcp4 . Discover , net . IPv4zero )
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 , frame . SrcMAC , 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 {
if _ , err := writer . Write ( frameLength ) ; err != nil {
log . Error ( "Error forwarding original packet length" , err )
}
if _ , err := writer . Write ( frameBytes ) ; err != nil {
log . Error ( "Error forwarding original packet data" , err )
}
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 {
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 {
log . Error ( "Error forwarding packet length" , err )
}
if _ , err := writer . Write ( newFrameBytes ) ; err != nil {
log . Error ( "Error forwarding packet data" , err )
}
}
}
// 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 , dstMAC net . HardwareAddr , srcMAC net . HardwareAddr , prefix string ) {
req := dhcp4 . Packet ( content )
if req . HLen ( ) > 16 { // Invalid size
log . Error ( prefix , "Invalid DHCP size" )
return
}
options := req . ParseOptions ( )
var reqType dhcp4 . MessageType
if t := options [ dhcp4 . OptionDHCPMessageType ] ; len ( t ) != 1 {
log . Error ( prefix , "Invalid DHCP message type" )
return
} else {
reqType = dhcp4 . MessageType ( t [ 0 ] )
if reqType < dhcp4 . Discover || reqType > dhcp4 . Inform {
log . Error ( prefix , "Invalid DHCP message type: " , reqType )
return
}
}
log . Debug ( prefix , "DHCP message registered: " , reqType )
if prefix == cmd . Out {
switch reqType {
case dhcp4 . Discover :
if OldIP == nil {
log . Fatal ( prefix , "DHCPDISCOVER but not previous address is known" )
}
sendDHCPReply ( req , dhcp4 . Offer , OldIP , options , dstMAC )
case dhcp4 . Inform :
fallthrough
case dhcp4 . Request :
reqIP := net . IP ( options [ dhcp4 . 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 , dhcp4 . ACK , reqIP , options , dstMAC )
}
} else {
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
opt := dhcp4 . Options {
dhcp4 . OptionSubnetMask : DHCPMask ,
dhcp4 . OptionRouter : RouterIP ,
dhcp4 . OptionDomainNameServer : DNSIP ,
} . SelectOrderOrAll ( reqOpt [ dhcp4 . OptionParameterRequestList ] )
// Creating the full packet layer by layer
buf := gopacket . NewSerializeBuffer ( )
opts := gopacket . SerializeOptions {
ComputeChecksums : true ,
FixLengths : true ,
}
eth := layers . Ethernet {
SrcMAC : DHCPMAC ,
DstMAC : dstMAC ,
EthernetType : layers . EthernetTypeIPv4 ,
}
ipv4 := layers . IPv4 {
Version : 4 ,
TTL : 128 ,
Protocol : layers . IPProtocolUDP ,
SrcIP : DHCPIP ,
DstIP : lease ,
}
udp := layers . UDP {
SrcPort : 67 ,
DstPort : 68 ,
}
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 )
}
if err := gopacket . SerializeLayers ( buf , opts , & eth , & ipv4 , & udp , gopacket . Payload ( dhcp ) ) ; err != nil {
log . Error ( "Error serializing DHCP response:" , err )
}
packetData := buf . Bytes ( )
// Sending layer through VM's pipe
packetLength := make ( [ ] byte , 2 )
binary . BigEndian . PutUint16 ( packetLength , uint16 ( len ( packetData ) ) )
if _ , err := VmWriter . Write ( packetLength ) ; err != nil {
log . Error ( "Error writing DHCP response length" , err )
}
if _ , err := VmWriter . Write ( packetData ) ; err != nil {
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 , & eth , & 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 )
}
}