Last active
May 8, 2016 13:38
-
-
Save marten-seemann/82c87f497d15a9b8a0ddff9ff46103d1 to your computer and use it in GitHub Desktop.
Packet dropping UDP proxy for testing QUIC
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Packet dropping UDP proxy for testing QUIC | |
package main | |
import ( | |
"bytes" | |
"flag" | |
"fmt" | |
"net" | |
"os" | |
"strings" | |
"sync" | |
"github.com/lucas-clemente/quic-go" | |
) | |
// Information maintained for each client/server connection | |
type Connection struct { | |
ClientAddr *net.UDPAddr // Address of the client | |
ServerConn *net.UDPConn // UDP connection to server | |
} | |
// Generate a new connection by opening a UDP connection to the server | |
func NewConnection(srvAddr, cliAddr *net.UDPAddr) *Connection { | |
conn := new(Connection) | |
conn.ClientAddr = cliAddr | |
srvudp, err := net.DialUDP("udp", nil, srvAddr) | |
if checkreport(1, err) { | |
return nil | |
} | |
conn.ServerConn = srvudp | |
return conn | |
} | |
// Global state | |
// Connection used by clients as the proxy server | |
var ProxyConn *net.UDPConn | |
// Address of server | |
var ServerAddr *net.UDPAddr | |
// Mapping from client addresses (as host:port) to connection | |
var ClientDict map[string]*Connection = make(map[string]*Connection) | |
// Mutex used to serialize access to the dictionary | |
var dmutex *sync.Mutex = new(sync.Mutex) | |
func setup(hostport string, port int) bool { | |
// Set up Proxy | |
saddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", port)) | |
if checkreport(1, err) { | |
return false | |
} | |
pudp, err := net.ListenUDP("udp", saddr) | |
if checkreport(1, err) { | |
return false | |
} | |
ProxyConn = pudp | |
Vlogf(2, "Proxy serving on port %d\n", port) | |
// Get server address | |
srvaddr, err := net.ResolveUDPAddr("udp", hostport) | |
if checkreport(1, err) { | |
return false | |
} | |
ServerAddr = srvaddr | |
Vlogf(2, "Connected to server at %s\n", hostport) | |
return true | |
} | |
func dlock() { | |
dmutex.Lock() | |
} | |
func dunlock() { | |
dmutex.Unlock() | |
} | |
// Go routine which manages connection from server to single client | |
func RunConnection(conn *Connection) { | |
buffer := make([]byte, 1500) | |
for { | |
// Read from server | |
n, err := conn.ServerConn.Read(buffer[0:]) | |
if checkreport(1, err) { | |
continue | |
} | |
r := bytes.NewReader(buffer) | |
publicHeader, err := quic.ParsePublicHeader(r) | |
drop := 11 | |
if int(publicHeader.PacketNumber)%drop == 0 { | |
Vlogf(3, "Dropping PacketNumber %d\n", publicHeader.PacketNumber) | |
continue | |
} | |
// drop := 2 | |
// if int(publicHeader.PacketNumber) == drop { | |
// Vlogf(3, "Dropping PacketNumber %d\n", publicHeader.PacketNumber) | |
// continue | |
// } | |
// Relay it to client | |
_, err = ProxyConn.WriteToUDP(buffer[0:n], conn.ClientAddr) | |
if checkreport(1, err) { | |
continue | |
} | |
Vlogf(3, "-> Connection 0x%x, PacketNumber %d\n", publicHeader.ConnectionID, publicHeader.PacketNumber) | |
} | |
} | |
// Routine to handle inputs to Proxy port | |
func RunProxy() { | |
buffer := make([]byte, 1500) | |
for { | |
n, cliaddr, err := ProxyConn.ReadFromUDP(buffer[0:]) | |
if checkreport(1, err) { | |
continue | |
} | |
r := bytes.NewReader(buffer) | |
publicHeader, err := quic.ParsePublicHeader(r) | |
drop := 11 | |
if int(publicHeader.PacketNumber)%drop == 0 { | |
Vlogf(3, "Dropping PacketNumber %d\n", publicHeader.PacketNumber) | |
continue | |
} | |
// drop := 6 | |
// if int(publicHeader.PacketNumber) == drop || int(publicHeader.PacketNumber) == drop+1 { | |
// Vlogf(3, "Dropping PacketNumber %d\n", publicHeader.PacketNumber) | |
// continue | |
// } | |
Vlogf(3, "<- Connection 0x%x, PacketNumber %d\n", publicHeader.ConnectionID, publicHeader.PacketNumber) | |
saddr := cliaddr.String() | |
dlock() | |
conn, found := ClientDict[saddr] | |
if !found { | |
conn = NewConnection(ServerAddr, cliaddr) | |
if conn == nil { | |
dunlock() | |
continue | |
} | |
ClientDict[saddr] = conn | |
dunlock() | |
Vlogf(2, "Created new connection for client %s\n", saddr) | |
// Fire up routine to manage new connection | |
go RunConnection(conn) | |
} else { | |
Vlogf(5, "Found connection for client %s\n", saddr) | |
dunlock() | |
} | |
// Relay to server | |
_, err = conn.ServerConn.Write(buffer[0:n]) | |
if checkreport(1, err) { | |
continue | |
} | |
} | |
} | |
var verbosity int = 6 | |
// Log result if verbosity level high enough | |
func Vlogf(level int, format string, v ...interface{}) { | |
if level <= verbosity { | |
// log.Printf(format, v...) | |
fmt.Printf(format, v...) | |
} | |
} | |
// Handle errors | |
func checkreport(level int, err error) bool { | |
if err == nil { | |
return false | |
} | |
Vlogf(level, "Error: %s", err.Error()) | |
return true | |
} | |
func main() { | |
var ihelp *bool = flag.Bool("h", false, "Show help information") | |
var ipport *int = flag.Int("p", 6667, "Proxy port") | |
var isport *int = flag.Int("P", 6666, "Server port") | |
var ishost *string = flag.String("H", "localhost", "Server address") | |
var iverb *int = flag.Int("v", 1, "Verbosity (0-6)") | |
// var idrop *float64 = flag.Float64("d", 0.0, "Packet drop rate") | |
flag.Parse() | |
verbosity = *iverb | |
if *ihelp { | |
flag.Usage() | |
os.Exit(0) | |
} | |
if flag.NArg() > 0 { | |
ok := true | |
fields := strings.Split(flag.Arg(0), ":") | |
ok = ok && len(fields) == 2 | |
if ok { | |
*ishost = fields[0] | |
n, err := fmt.Sscanf(fields[1], "%d", isport) | |
ok = ok && n == 1 && err == nil | |
} | |
if !ok { | |
flag.Usage() | |
os.Exit(0) | |
} | |
} | |
hostport := fmt.Sprintf("%s:%d", *ishost, *isport) | |
Vlogf(3, "Proxy port = %d, Server address = %s\n", | |
*ipport, hostport) | |
if setup(hostport, *ipport) { | |
RunProxy() | |
} | |
os.Exit(0) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment