Skip to content

Instantly share code, notes, and snippets.

@warriorpaw
Created September 4, 2018 14:02
Show Gist options
  • Save warriorpaw/85c26a928c01cf4233ea3198dc71a09c to your computer and use it in GitHub Desktop.
Save warriorpaw/85c26a928c01cf4233ea3198dc71a09c to your computer and use it in GitHub Desktop.
Fake vxlan "server": use tap interface to simulate vxlan for kernel < 3.7 AND ipv4 only; on "client" side set this "server" as remote; hardcode VxlanId/ip/MTU etc...;; Do not use this in production environment ; just for fun.
package main
import (
"github.com/songgao/water"
"net"
"fmt"
"os"
"os/signal"
"syscall"
"unsafe"
"bytes"
"encoding/binary"
"os/exec"
"sync"
)
type vxlanHeader struct {
Flags uint16
Group uint16
VxlanId uint32
}
func errorPanic(err error, format string, a ...interface{}) {
if err != nil {
fmt.Printf("Panic : "+format, a...)
panic(err)
}
}
func errorPrint(format string, a ...interface{}) {
if a != nil && len(a) > 0 {
fmt.Printf("Error : "+format, a...)
} else {
fmt.Printf("Error : " + format)
}
}
func getUdpConn(port int) (ret *net.UDPConn, err error) {
ret, err = net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("0.0.0.0"), Port: port})
return
}
type ifReqMtu struct {
Name [0x10]byte
Mtu int32
pad [0x28 - 0x10 - 4]byte
}
var innerIpMap = make(map[uint32]net.IP)
var mapLock sync.RWMutex
func getPackageOffset(p []byte) int {
if p[12] == 0x81 && p[13] == 0x00 {
return 4
} else if p[12] == 0x88 && p[13] == 0xa8 {
return 8
}
return 0
}
func parseTargetIP(p []byte) (ret net.IP, err error) {
offset := getPackageOffset(p)
err = nil
if p[12+offset] == 0x08 && p[13+offset] == 0x00 {
// ipv4
ipPos := 14 + offset + 16
ret = net.IP(p[ipPos : ipPos+4 : ipPos+4])
return
} else if p[12+offset] == 0x08 && p[13+offset] == 0x06 {
// arp
if p[14+offset+2] == 0x08 && p[14+offset+3] == 0x00 {
// arp for ipv4
ipPos := 14 + offset + 24
ret = net.IP(p[ipPos : ipPos+4 : ipPos+4])
return
}
}
ret = nil
err = fmt.Errorf("unsupported package")
return
}
func parseSenderIP(p []byte) (ret net.IP, err error) {
offset := getPackageOffset(p)
err = nil
if p[12+offset] == 0x08 && p[13+offset] == 0x00 {
// ipv4
ipPos := 14 + offset + 12
ret = net.IP(p[ipPos : ipPos+4 : ipPos+4])
return
} else if p[12+offset] == 0x08 && p[13+offset] == 0x06 {
// arp
if p[14+offset+2] == 0x08 && p[14+offset+3] == 0x00 {
// arp for ipv4
ipPos := 14 + offset + 14
ret = net.IP(p[ipPos : ipPos+4 : ipPos+4])
return
}
}
ret = nil
err = fmt.Errorf("unsupported package")
return
}
func setMTU() {
var req ifReqMtu
req.Mtu = 1350
// Physical interface MTU - 50
copy(req.Name[:], []byte("vxlan0"))
tmpUdp, err := getUdpConn(0)
errorPanic(err, "error in get udp")
defer tmpUdp.Close()
fileUdp, _ := tmpUdp.File()
fd := fileUdp.Fd()
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.SIOCSIFMTU), uintptr(unsafe.Pointer(&req)))
if errno != 0 {
fmt.Printf("SIOCSIFMTU syscall return %d\n", errno)
panic(fmt.Errorf("SIOCSIFMTU syscall return %d\n", errno))
}
}
func handleRecv(s *net.UDPConn, tapIF *water.Interface) {
data := make([]byte, 1500)
for {
data = data[0:1500]
n, addr, err := s.ReadFromUDP(data)
if err != nil {
errorPrint("error in handleRecv s.ReadFromUDP %+v \n", err)
return
}
if n < 42 { // min vxlan 8 + inner eth 14 + ip 20
errorPrint("size error, drop package \n")
continue
}
var h vxlanHeader
buf := bytes.NewReader(data[:8])
err = binary.Read(buf, binary.BigEndian, &h)
if err != nil {
errorPrint("error in binary.Read %+v \n", err)
continue
}
if h.VxlanId != 512 {
// VxlanId hardcode in 2;; ignore flags and Group
errorPrint("vxlanId not match %d \n", h.VxlanId)
continue
}
sendData := data[8:n]
ip, err := parseSenderIP(sendData)
if err == nil {
// fmt.Printf("recv from %s \n", ip.String())
intIP := binary.BigEndian.Uint32([]byte(ip))
v, exist := innerIpMap[intIP]
if exist == false || addr.IP.Equal(v) == false {
mapLock.Lock()
innerIpMap[intIP] = addr.IP
mapLock.Unlock()
}
}
sendBytes, err := tapIF.Write(sendData)
if sendBytes != (n-8) || err != nil {
errorPrint("tapIF.Write err %d -> , %+v \n", n, sendBytes, err)
}
}
}
func handleSend(s *net.UDPConn, tapIF *water.Interface) {
data := make([]byte, 1500)
h := &vxlanHeader{Flags: 0x0800, Group: 0, VxlanId: 2 * 256}
// VxlanId hardcode in 2
buf := new(bytes.Buffer)
err := binary.Write(buf, binary.BigEndian, h)
errorPanic(err, "binary.Write error \n")
copy(data[:8], buf.Bytes())
for {
n, err := tapIF.Read(data[8:])
if err != nil {
errorPrint("error in handleSend tapIF.Read %+v \n", err)
return
}
ip, err := parseTargetIP(data[8:])
if err != nil {
// errorPrint("only ipv4 supported\n")
continue
}
// fmt.Printf("sendto %s \n", ip.String())
intIP := binary.BigEndian.Uint32([]byte(ip))
mapLock.RLock()
remoteIP, exist := innerIpMap[intIP]
mapLock.RUnlock()
if exist == false {
// errorPrint("remote ip not found \n")
continue
}
remote := &net.UDPAddr{IP: remoteIP, Port: 4789}
sendData := data[:n+8]
n, err = s.WriteToUDP(sendData, remote)
if err != nil {
errorPrint("error in handleSend s.WriteToUDP %+v \n", err)
continue
}
}
}
func main() {
config := water.Config{
DeviceType: water.TAP,
}
config.Name = "vxlan0"
tapIF, err := water.New(config)
errorPanic(err, "water.New if error")
setMTU()
cmd := exec.Command("ip", "addr", "add", "172.30.60.1/24", "dev", "vxlan0")
err = cmd.Run()
errorPanic(err, "set ip error %+v \n", err)
cmd = exec.Command("ip", "link", "set", "vxlan0", "up")
err = cmd.Run()
errorPanic(err, "set link up error %+v \n", err)
recvSocket, err := getUdpConn(4789)
errorPanic(err, "get socket error")
go handleRecv(recvSocket, tapIF)
go handleSend(recvSocket, tapIF)
signalChan := make(chan os.Signal, 2)
signal.Notify(signalChan, os.Interrupt)
<-signalChan
recvSocket.Close()
tapIF.Close()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment