Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement l2fwd-like app #736

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
31 changes: 22 additions & 9 deletions src/apps/intel/loadgen.lua
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ local can_transmit, transmit

LoadGen = {}

function LoadGen:new (pciaddress)
local o = { pciaddress = pciaddress,
dev = intel10g.new_sf({pciaddr=pciaddress}) }
function LoadGen:new (conf)
local o = { pciaddr= conf.pciaddr,
dev = intel10g.new_sf({pciaddr=conf.pciaddr}),
report_rx = conf.report_rx, }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What exactly do the changes to LoadGen do? Does the LoadGen documentation need an update to reflect conf.report_rx?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I modified LoadGen so it reports on received packets as well as transmitted packets. As traffic gets bounced to its originator and the NIC is locked by the program running the packetblaster, this was the only possible way I thought of of checking traffic gets actually bounced back.

I missed updating LoadGen docs, if the change finally makes it I will update the docs.

o.dev:open()
o.dev:wait_linkup()
disable_tx_descriptor_writeback(o.dev)
Expand Down Expand Up @@ -69,10 +70,22 @@ function LoadGen:pull ()
end

function LoadGen:report ()
print(self.pciaddress,
"TXDGPC (TX packets)", lib.comma_value(tonumber(self.dev.s.TXDGPC())),
"GOTCL (TX octets)", lib.comma_value(tonumber(self.dev.s.GOTCL())))
self.dev.s.TXDGPC:reset()
self.dev.s.GOTCL:reset()
end
local s = self.dev.s
local function format(reg)
return lib.comma_value(tonumber(reg()))
end

print(self.pciaddr,
"TXDGPC (TX packets)", format(s.TXDGPC),
"GOTCL (TX octets)", format(s.GOTCL))
s.TXDGPC:reset()
s.GOTCL:reset()
if self.report_rx then
print(self.pciaddr,
-- TODO: RXDGPC reported 0 packets received, but non-filtered got packets.
"RXNFGPC (RX packets)", format(s.RXNFGPC),
"GORCL (RX octets)", format(s.GORCL))
s.RXNFGPC:reset()
s.GORCL:reset()
end
end
11 changes: 11 additions & 0 deletions src/program/l2fwd/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Usage: l2fwd <PCI1> <PCI2>

PCI1: [pciaddr|virtio:pciaddr]
PCI2: [pciaddr|virtio:pciaddr]

Creates a full-duplex softwire between PCI1 and PCI2.

Examples:

l2fwd 0000:01:00.0 0000:02:00.0
l2fwd virtio:0000:01:00.0 virtio:0000:02:00.0
1 change: 1 addition & 0 deletions src/program/l2fwd/README.inc
84 changes: 84 additions & 0 deletions src/program/l2fwd/l2fwd.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
module(...,package.seeall)

local lib = require("core.lib")
local main = require("core.main")

local function show_usage(code)
print(require("program.l2fwd.README_inc"))
main.exit(code)
end

local function parse_args(args)
local handlers = {}
local opts = {}
function handlers.h() show_usage(0) end
function handlers.v()
opts.verbose = true
end
function handlers.D(arg)
opts.duration = assert(tonumber(arg), "duration must be a number")
end
args = lib.dogetopt(args, handlers, "hvD:", { help="h", verbose="v", duration="D"})
if #args ~= 2 then show_usage(1) end
return opts, unpack(args)
end

local function parse_nic_driver(arg)
local driver_class, pciaddr = arg:match("(%a+):([%w:.]+)")
if not driver_class then return "pci", arg end
return driver_class, pciaddr
end

local function config_nic(c, app_name, pciaddr)
local driver
local driver_class, pciaddr = parse_nic_driver(pciaddr)
if driver_class == "virtio" then
driver = require("apps.virtio_net.virtio_net").VirtioNet
config.app(c, app_name, driver, {pciaddr = pciaddr})
else
assert(driver_class == "pci",
("Not supported driver class '%s'"):format(driver_class))
driver = require("apps.intel.intel_app").Intel82599
config.app(c, app_name, driver, {pciaddr = pciaddr})
end
end

function run(args)
local opts, arg1, arg2 = parse_args(args)
local c = config.new()

config_nic(c, "nic1", arg1)
config_nic(c, "nic2", arg2)

config.link(c, "nic1.tx -> nic2.rx")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we get also config.link(c, "nic2.tx -> nic1.rx") here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nnikolaev-virtualopensystems Am I right in assuming that would make l2fwd a two-way street?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly, this makes it full featured 2-way "forwarder".

config.link(c, "nic2.tx -> nic1.rx")

if opts.verbose then
local fn = function()
print("Report (last 1 sec):")
engine.report_links()
engine.report_load()
end
local t = timer.new("report", fn, 1e9, 'repeating')
timer.activate(t)
end

engine.configure(c)
if opts.duration then
engine.main({duration=opts.duration})
else
engine.main()
end
end

function selftest()
print("selftest: l2fwd")
local driver_class, pciaddr
driver_class, pciaddr = parse_nic_driver("virtio:0000:00:01.0")
assert(driver_class == "virtio" and pciaddr == "0000:00:01.0")
driver_class, pciaddr = parse_nic_driver("pci:0000:00:01.0")
assert(driver_class == "pci" and pciaddr == "0000:00:01.0")
driver_class, pciaddr = parse_nic_driver("0000:00:01.0")
assert(driver_class == "pci" and pciaddr == "0000:00:01.0")
print("OK")
end
1 change: 1 addition & 0 deletions src/program/packetblaster/README
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
Usage:
packetblaster bounce
packetblaster replay
packetblaster synth

Expand Down
14 changes: 14 additions & 0 deletions src/program/packetblaster/bounce/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Usage: packetblaster bounce [OPTIONS] <PCAPFILE> <PCI1> <PCI2>

-D DURATION, --duration DURATION
Run for DURATION seconds.
Default: unlimited
-h, --help
Print usage information.

packetblaster transmits packets continuously to <PCI1> network adapter.
Packets will eventually reach <PCI2>, if both network adapters are wired together.
Packets bounce on <PCI2> and get back to <PCI1>.

Examples:
packetblaster bounce myfile.cap 0000:01:00.0 0000:01:00.1
1 change: 1 addition & 0 deletions src/program/packetblaster/bounce/README.inc
166 changes: 109 additions & 57 deletions src/program/packetblaster/packetblaster.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,87 +11,139 @@ local main = require("core.main")
local PcapReader= require("apps.pcap.pcap").PcapReader
local Synth = require("apps.test.synth").Synth
local LoadGen = require("apps.intel.loadgen").LoadGen
local Intel82599 = require("apps.intel.intel_app").Intel82599
local lib = require("core.lib")
local ffi = require("ffi")
local C = ffi.C

local usage = require("program.packetblaster.README_inc")
local usage_replay = require("program.packetblaster.replay.README_inc")
local usage_synth = require("program.packetblaster.synth.README_inc")
local mode

local long_opts = {
duration = "D",
help = "h",
src = "s",
dst = "d",
sizes = "S"
}
local function show_usage (code)
if mode == 'replay' then
print(require("program.packetblaster.replay.README_inc"))
elseif mode == 'synth' then
print(require("program.packetblaster.synth.README_inc"))
elseif mode == 'bounce' then
print(require("program.packetblaster.bounce.README_inc"))
else
print(require("program.packetblaster.README_inc"))
end
main.exit(code)
end

function run (args)
local opt = {}
local mode = table.remove(args, 1)
local duration
local c = config.new()
function opt.D (arg)
duration = assert(tonumber(arg), "duration is not a number!")
local function parse_args (args, short_opts, long_opts)
local handlers = {}
local opts = {}
function handlers.D (arg)
opts.duration = assert(tonumber(arg), "duration is not a number!")
end
function opt.h (arg)
if mode == 'replay' then print(usage_replay)
elseif mode == 'synth' then print(usage_synth)
else print(usage) end
main.exit(1)
function handlers.h (arg)
show_usage(0)
end
if mode == 'replay' and #args > 1 then
args = lib.dogetopt(args, opt, "hD:", long_opts)
function handlers.s (arg) opts.source = arg end
function handlers.d (arg) opts.destination = arg end
function handlers.S (arg)
local sizes = {}
for size in string.gmatch(arg, "%d+") do
sizes[#sizes+1] = tonumber(size)
end
opts.sizes = sizes
end
args = lib.dogetopt(args, handlers, short_opts, long_opts)
if (mode == 'replay' and #args <= 1) or
(mode == 'synth' and #args < 1) or
(mode == 'bounce' and not #args == 3) then
show_usage(1)
end
return opts, args
end

local function report_fn ()
print("Transmissions (last 1 sec):")
engine.report_apps()
end

function run (args)
local opts, c
mode = table.remove(args, 1)
if mode == 'replay' then
c = config.new()
opts, args = parse_args(args, "hD:", {help="h", duration="D"})
local filename = table.remove(args, 1)
config.app(c, "pcap", PcapReader, filename)
config.app(c, "loop", basic_apps.Repeater)
config.app(c, "source", basic_apps.Tee)
config.link(c, "pcap.output -> loop.input")
config.link(c, "loop.output -> source.input")
elseif mode == 'synth' and #args >= 1 then
local source
local destination
local sizes
function opt.s (arg) source = arg end
function opt.d (arg) destination = arg end
function opt.S (arg)
sizes = {}
for size in string.gmatch(arg, "%d+") do
sizes[#sizes+1] = tonumber(size)
end
config_sources(c, args)
elseif mode == 'synth' then
c = config.new()
opts, args = parse_args(args, "hD:s:d:S:", {help="h", duration="D",
src="s", dst="d", sizes="S"})
config.app(c, "source", Synth, { sizes = opts.sizes,
src = opts.source,
dst = opts.destination })
config_sources(c, args)
elseif mode == 'bounce' then
c = config.new()
opts, args = parse_args(args, "hD:", {help="h", duration="D"})
local filename, nic, bouncer = unpack(args)
config.app(c, "pcap", PcapReader, filename)
config.app(c, "loop", basic_apps.Repeater)
config.app(c, "source", basic_apps.Tee)
config.link(c, "pcap.output -> loop.input")
config.link(c, "loop.output -> source.input")
local nics = config_sources(c, {nic}, {report_rx = true})
assert(#nics == 1, "Too many nics")
config.app(c, "bouncer", Intel82599, {
pciaddr = bouncer
})
config.link(c, "bouncer.tx -> bouncer.rx")
local nic_name = nics[1]
report_fn = function()
print("Transmissions and receptions (last 1 sec):")
engine.app_table[nic_name]:report()
end

args = lib.dogetopt(args, opt, "hD:s:d:S:", long_opts)
config.app(c, "source", Synth, { sizes = sizes,
src = source,
dst = destination })
else
opt.h()
end
local patterns = args
local nics = 0
pci.scan_devices()
for _,device in ipairs(pci.devices) do
if is_device_suitable(device, patterns) then
nics = nics + 1
local name = "nic"..nics
config.app(c, name, LoadGen, device.pciaddress)
config.link(c, "source."..tostring(nics).."->"..name..".input")
end
show_usage(1)
end
assert(nics > 0, "<PCI> matches no suitable devices.")

engine.busywait = true
intel10g.num_descriptors = 32*1024
engine.configure(c)
local fn = function ()
print("Transmissions (last 1 sec):")
engine.report_apps()
engine.app_table.nic1:report()
end
local t = timer.new("report", fn, 1e9, 'repeating')
local t = timer.new("report", report_fn, 1e9, 'repeating')
timer.activate(t)
if duration then engine.main({duration=duration})
else engine.main() end
if opts.duration then engine.main({duration=opts.duration})
else engine.main() end
end

function config_sources (c, patterns, opts)
local nic_name = (function()
local id = 0
return function()
id = id + 1
return id, "nic"..id
end
end)()
local nics = {}
pci.scan_devices()
for _,device in ipairs(pci.devices) do
if is_device_suitable(device, patterns) then
local id, name = nic_name()
config.app(c, name, LoadGen, {
pciaddr = device.pciaddress,
report_rx = opts and opts.report_rx or false,
})
config.link(c, ("source.%d -> %s.input"):format(id, name))
table.insert(nics, name)
end
end
assert(#nics > 0, "<PCI> matches no suitable devices.")
return nics
end

function is_device_suitable (pcidev, patterns)
Expand Down