111 lines
3.1 KiB
Go
111 lines
3.1 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"math"
|
|
"net"
|
|
"net/netip"
|
|
|
|
"golang.org/x/net/ipv6"
|
|
)
|
|
|
|
func vx46(natprefix netip.Addr, upstreamAddr netip.Addr, port uint16, mtu uint16) error {
|
|
upstream := netip.AddrPortFrom(upstreamAddr, port)
|
|
p, err := net.ListenUDP("udp", &net.UDPAddr{Port: int(port)})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// we need to know to which address incoming ipv6 packets were sent to
|
|
// extract the client ipv4 and port
|
|
ipv6.NewPacketConn(p).SetControlMessage(ipv6.FlagDst, true)
|
|
|
|
defer p.Close()
|
|
|
|
var b [math.MaxUint16]byte
|
|
var oob [20480]byte // from /proc/sys/net/core/optmem_max
|
|
for {
|
|
n, oobn, _, ingressSrcAddrPort, err := p.ReadMsgUDPAddrPort(b[:], oob[:])
|
|
|
|
cm := ipv6.ControlMessage{}
|
|
if err := cm.Parse(oob[:oobn]); err != nil {
|
|
return err
|
|
}
|
|
|
|
ingressDstAddr, ok := netip.AddrFromSlice(cm.Dst)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
var ingressDstAddrPort netip.AddrPort
|
|
ingressDstAddrPort = netip.AddrPortFrom(ingressDstAddr.Unmap(), port)
|
|
ingressSrcAddrPort = netip.AddrPortFrom(ingressSrcAddrPort.Addr().Unmap(), ingressSrcAddrPort.Port())
|
|
|
|
var egressDstAddrPort netip.AddrPort
|
|
var egressOOB []byte
|
|
|
|
if ingressSrcAddrPort.Addr().Is4() {
|
|
// embed the "client" ipv4 into the src address for the packet to the upstream vxlan ipv6 endpoint
|
|
// the destination is the upstream vxlan endpoint
|
|
inaddr4 := ingressSrcAddrPort.Addr().As4()
|
|
egressSrcAddr := natprefix.As16()
|
|
copy(egressSrcAddr[10:14], inaddr4[:])
|
|
binary.BigEndian.PutUint16(egressSrcAddr[14:16], ingressSrcAddrPort.Port())
|
|
|
|
cm := ipv6.ControlMessage{Src: net.IP(egressSrcAddr[:])}
|
|
egressOOB = cm.Marshal()
|
|
egressDstAddrPort = upstream
|
|
} else {
|
|
inaddr6 := ingressDstAddrPort.Addr().As16()
|
|
egressDstAddrPort = netip.AddrPortFrom(
|
|
netip.AddrFrom4([4]byte(inaddr6[10:14])), // extract client ipv4
|
|
binary.BigEndian.Uint16(inaddr6[14:16]), // extract client port
|
|
)
|
|
}
|
|
|
|
outn, outoobn, err := p.WriteMsgUDPAddrPort(b[:n], egressOOB, egressDstAddrPort)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if outn != n {
|
|
return fmt.Errorf("dropped bytes: %d sent of %d", outn, n)
|
|
}
|
|
if outoobn != len(egressOOB) {
|
|
return fmt.Errorf("dropped oob bytes: %d sent of %d", outoobn, len(egressOOB))
|
|
}
|
|
}
|
|
}
|
|
func main() {
|
|
natprefixStr := flag.String("prefix", "", "local IPv6 base address for a /80 to use for communication with upstream")
|
|
upstreamStr := flag.String("upstream", "", "IPv6 address of the upstream VXLAN endpoint")
|
|
portInt := flag.Uint("port", 8472, "port for vxlan communication")
|
|
mtuInt := flag.Uint("mtu", 1422, "buffer size")
|
|
|
|
flag.Parse()
|
|
|
|
natprefix, err := netip.ParseAddr(*natprefixStr)
|
|
if err != nil {
|
|
log.Fatalf("Invalid prefix: %s", err)
|
|
}
|
|
upstream, err := netip.ParseAddr(*upstreamStr)
|
|
if err != nil {
|
|
log.Fatalf("Invalid upstream: %s", err)
|
|
}
|
|
if *portInt > math.MaxUint16 {
|
|
log.Fatalf("port out of range: %d", *portInt)
|
|
}
|
|
port := uint16(*portInt)
|
|
|
|
if *mtuInt > math.MaxUint16 {
|
|
log.Fatalf("mtu out of range: %d", *portInt)
|
|
}
|
|
mtu := uint16(*mtuInt)
|
|
|
|
if err := vx46(natprefix, upstream, port, mtu); err != nil {
|
|
log.Println(err)
|
|
}
|
|
}
|