Compare commits

...

5 Commits

8 changed files with 400 additions and 149 deletions

View File

@ -85,6 +85,7 @@
["vtunnel"] = { ["vtunnel"] = {
files = { files = {
["master/vTunnel/interminitel.lua"] = "/lib", ["master/vTunnel/interminitel.lua"] = "/lib",
["master/vTunnel/OpenOS/usr/man/vtunnel"] = "/man",
["master/vTunnel/OpenOS/etc/rc.d/vtunnel.lua"] = "//etc/rc.d", ["master/vTunnel/OpenOS/etc/rc.d/vtunnel.lua"] = "//etc/rc.d",
}, },
dependencies = { dependencies = {

View File

@ -1,88 +1,142 @@
local vcomp = require "vcomponent" local vcomponent = require "vcomponent"
local serial = require "serialization"
local component = require "component" local component = require "component"
local computer = require "computer" local computer = require "computer"
local imt = require "interminitel"
local event = require "event" local event = require "event"
local internet = component.internet local imt = require "interminitel"
local addr, raddr = vcomp.uuid(),vcomp.uuid() local cfg = {}
local poll = 0.5 cfg.peers = {}
local listener, timer cfg.rtimer = 5
local socket cfg.katimer = 30
local listeners = {}
local timers = {}
local proxies = {}
-- dumb keepalive stuff local function loadcfg()
local keepalive = 30 local f = io.open("/etc/vtunnel.cfg","rb")
local katimer if not f then return false end
for k,v in pairs(serial.unserialize(f:read("*a")) or {}) do
function start(faddr) cfg[k] = v
if listener then return end
if faddr then
local host,nport = faddr:match("(.+):(%d+)")
end end
local iaddr,port = host or faddr or "shadowkat.net", tonumber(nport) or 4096 f:close()
socket = internet.connect(iaddr,port) end
print("Connecting to "..iaddr..":"..tostring(port).."...") local function savecfg()
repeat local f = io.open("/etc/vtunnel.cfg","wb")
os.sleep(0.5) if not f then
until socket.finishConnect() print("Warning: unable to save configuration.")
print("Connected!") return false
end
local proxy = {} f:write(serial.serialize(cfg))
local rbuffer = "" f:close()
local timer = nil end
function listener(t) local function createTunnel(host,port,addr,raddr)
rbuffer=rbuffer..(socket.read(4096) or "") local proxy = {address=addr,buffer=""}
if imt.decodePacket(rbuffer) then function proxy.connect()
computer.pushSignal("modem_message",addr,raddr,0,0,imt.decodePacket(rbuffer)) if proxy.socket then
rbuffer = imt.getRemainder(rbuffer) or "" proxy.socket.close()
end end
if t == "internet_ready" and timer then proxy.socket = component.internet.connect(host,port)
event.cancel(timer) local st = computer.uptime()
timer = nil repeat
end coroutine.yield()
until proxy.socket.finishConnect() or computer.uptime() > st+5
end end
timer = event.timer(poll,listener,math.huge) -- this is only here because OCEmu doesn't do internet_ready
event.listen("internet_ready",listener)
-- also dumb keepalive stuff, fix this later
katimer = event.timer(keepalive,function()
socket.write("\0\1\0")
end,math.huge)
function proxy.send(...) function proxy.send(...)
socket.write(imt.encodePacket(...)) rt = 0
while not proxy.socket.write(imt.encodePacket(...)) and rt < 10 do
proxy.connect()
rt = rt + 1
end
proxy.last = computer.uptime()
end end
function proxy.read()
function proxy.maxPacketSize() local rb, r
return 12288 local rt = 0
while true do
rb,r = proxy.socket.read(4096)
if rb or rt > 10 then break end
if type(rb) == "nil" then
proxy.connect()
end
rt = rt + 1
end
proxy.buffer = proxy.buffer .. rb
while imt.decodePacket(proxy.buffer) do
computer.pushSignal("modem_message",addr,raddr,0,0,imt.decodePacket(proxy.buffer))
proxy.buffer = imt.getRemainder(proxy.buffer) or ""
end
if computer.uptime() > proxy.last + cfg.katimer then
proxy.socket.write("\0\1\0")
proxy.last = computer.uptime()
end
end end
event.listen("internet_ready",proxy.read)
proxy.type = "tunnel" listeners[addr] = {"internet_ready",proxy.read}
proxy.slot = 4096 timers[addr] = event.timer(cfg.rtimer, proxy.read, math.huge)
proxy.connect()
local docs = { proxy.last = computer.uptime()
send = "function(data...) -- Sends the specified data to the card this one is linked to.", return proxy
maxPacketSize = "function():number -- Gets the maximum packet size (config setting)."
}
vcomp.register(addr,"tunnel",proxy,docs)
end end
function stop() function start()
if listener then loadcfg()
event.ignore("internet_ready",listener) for k,v in pairs(cfg.peers) do
listener = nil print(string.format("Connecting to %s:%d",v.host,v.port))
v.addr = v.addr or vcomponent.uuid()
v.raddr = v.raddr or vcomponent.uuid()
local px = createTunnel(v.host, v.port, v.addr, v.raddr)
vcomponent.register(v.addr, "tunnel", px)
proxies[v.addr] = px
end end
if timer then end
event.cancel(timer) function stop()
timer = nil for k,v in pairs(listeners) do
end event.ignore(v[1],v[2])
if katimer then end
event.cancel(katimer) for k,v in pairs(timers) do
katimer = nil event.cancel(v)
end end
if component.type(addr) then for k,v in pairs(proxies) do
vcomp.unregister(addr) vcomponent.unregister(k)
end end
socket.close() end
function settimer(time)
time = tonumber(time)
if not time then
print("Timer must be a number.")
return false
end
cfg.rtime = time
savecfg()
end
function addpeer(host,port)
port = tonumber(port) or 4096
local t = {}
t.host = host
t.port = port
t.addr = vcomponent.uuid()
t.raddr = vcomponent.uuid()
cfg.peers[#cfg.peers+1] = t
print(string.format("Added peer #%d (%s:%d) to the configuration.\nRestart to apply changes.",#cfg.peers,host,port))
savecfg()
end
function listpeers()
for k,v in pairs(cfg.peers) do
print(string.format("#%d (%s:%d)\n Local address: %s\n Remote address: %s",k,v.host,v.port,v.addr,v.raddr))
end
end
function delpeer(n)
n=tonumber(n)
if not n then
print("delpeer requires a number, representing the peer number, as an argument.")
return false
end
local dp = table.remove(cfg.peers, n)
savecfg()
print(string.format("Removed peer %s:%d",dp.host, dp.port))
end end

View File

@ -0,0 +1,75 @@
# vTunnel - TCP-based Linked Card Emulator
vTunnel can be used to add bridging over the internet to any existing OpenOS software that uses linked cards.
Despite originally being written for Minitel, vTunnel implements a fully-functional linked card emulator and a server that will run under most unix-likes (OpenBSD is currently somewhat flaky, Linux is recommended).
The protocol is documented [here](vtunnel-protocol.md)
## Setup
### Server Requirements
- Some form of unix-like
- Lua 5.2 or 5.3
- Luasocket
### Client Installation
#### OPPM
```
oppm install vtunnel
```
#### Manual
1. Install vtunnel.lua to /etc/rc.d
2. Install interminitel.lua to /usr/lib
### Client setup
#### Creating a connection
Many connections can be configured. To add one:
```
rc vtunnel addpeer <server address> <server port>
```
## Usage
### Running the server
At present, all you need to do is run bridge.lua, for example:
```
lua53 bridge.lua [port] [timeout]
```
### Client
vTunnel is invoked as follows:
```
rc vtunnel start
```
vTunnel can also be configured to start automatically:
```
rc vtunnel enable
```
There are several other commands available under `rc vtunnel`:
- settimer - allows you to configure how often vTunnel polls the TCP socket
- listpeers - shows which peers are configured, and the associated tunnel card addresses
- delpeer - deletes a peer from the configuration, as shown in listpeers
### Minitel configuration
1. Disable minitel with rc - `rc minitel disable`
2. Enable vtunnel with rc - `rc vtunnel enable`
3. Enable minitel with rc - `rc minitel enable`
This will ensure that Minitel starts later than vTunnel, allowing it to see the virtual tunnel component.

View File

@ -1,72 +0,0 @@
# vTunnel - TCP-based Linked Card Emulator
vTunnel can be used to add bridging over the internet to any existing OpenOS software that uses linked cards.
Despite originally being written for Minitel, vTunnel implements a fully-functional linked card emulator and a server that will run under most unix-likes (OpenBSD is currently somewhat flaky, Linux is recommended).
The protocol is documented [here](vtunnel-protocol.md)
## Setup
### Server
#### Requirements
- Some form of unix-like
- Lua 5.2 or 5.3
- Luasocket
#### Running the server
At present, all you need to do is run bridge.lua, for example:
```
lua53 bridge.lua [port] [timeout]
```
### Client
#### OPPM
```
oppm install vtunnel
```
#### Manual
1. Install vtunnel.lua to /etc/rc.d
2. Install interminitel.lua to /usr/lib
#### Running
vTunnel is invoked as follows:
```
rc vtunnel start <server address>:<server port>
```
This will create a virtual linked card component connected to server\_address:server\_port.
#### Configuration
vTunnel can also be configured to start automatically. First, one would enable the rc service:
```
rc vtunnel enable
```
Then, edit /etc/rc.cfg and add a line as follows:
```
vtunnel = "server.address:port"
```
Leaving it as default will result in vTunnel connecting to the public SKS bridge.
#### Minitel configuration
1. Disable minitel with rc - `rc minitel disable`
2. Enable vtunnel with rc - `rc vtunnel enable`
3. Enable minitel with rc - `rc minitel enable`
This will ensure that Minitel starts later than vTunnel, allowing it to see the virtual tunnel component.

1
vTunnel/README.md Symbolic link
View File

@ -0,0 +1 @@
OpenOS/usr/man/vtunnel

View File

@ -0,0 +1,70 @@
local imtp = Proto("imt","InterMinitel")
local imt = require "interminitel"
local pid = ProtoField.string("imt.packet_id","Packet ID")
local ptype = ProtoField.int32("imt.packet_type","Packet type")
local dest = ProtoField.string("imt.destination","Destination address")
local src = ProtoField.string("imt.source","Source address")
local port = ProtoField.int32("imt.port","Port")
local data = ProtoField.string("imt.data","Data")
local conv = ProtoField.int32("imt.conversation","Conversation")
local cport = ProtoField.int32("imt.conversation_port","Conversation Port")
local seg = ProtoField.string("imt.segment","Segment")
imtp.fields = {pid, ptype, dest, src, port, data, conv, cport}
local conversations = {}
local function checkConversation(p,c)
if p[5] == c.port and c.addresses[p[4]] and c.addresses[p[3]] then
return true
end
return false
end
local conversationPackets = {}
function imtp.dissector(buffer,pinfo,tree)
pinfo.cols.protocol=imtp.name
local d = buffer():string()
local dp, sp = imt.decodePacket(d)
local subtree = tree:add(imtp,"InterMinitel Data")
for k,v in pairs(dp) do
subtree:add(imtp.fields[k], buffer(sp[k], tostring(v):len()), v)
end
pinfo.cols.src = dp[4]
pinfo.cols.dst = dp[3]
pinfo.cols.src_port = dp[5]
pinfo.cols.dst_port = dp[5]
if dp[2] == "0" then
pinfo.cols.info = "Unreliable packet."
elseif dp[2] == "1" then
pinfo.cols.info = "Reliable packet."
elseif dp[2] == "2" then
pinfo.cols.info = "Acknowledgement packet for '"..dp[6].."'"
end
if dp[6] == "openstream" then
local t = {addresses = {}, iport = dp[5]}
t.addresses[dp[3]] = true
t.addresses[dp[4]] = true
t.id = conversationPackets[dp[1]] or #conversations+1
print("Conversation started by "..dp[4].." talking to "..dp[3].." on port "..tostring(dp[5]))
conversations[t.id] = t
subtree:add(conv, t.id):set_generated()
subtree:add(cport, t.iport):set_generated()
conversationPackets[dp[1]] = t.id
end
for k,v in pairs(conversations) do
if not v.port and v.iport == dp[5] and tonumber(dp[6]) and v.addresses[dp[3]] and v.addresses[dp[4]] then
v.port = tostring(tonumber(dp[6]))
print(string.format("Conversation %d port set to %d",k,v.port))
subtree:add(conv, v.id):set_generated()
subtree:add(cport, v.iport):set_generated()
conversationPackets[dp[1]] = v.id
elseif checkConversation(dp,v) then
print(dp[1].." is part of "..tostring(k))
subtree:add(conv, v.id):set_generated()
subtree:add(cport, v.iport):set_generated()
conversationPackets[dp[1]] = v.id
end
end
end

View File

@ -0,0 +1,66 @@
local imt = {}
imt.ttypes = {}
imt.ttypes.string=1
imt.ttypes.number=2
imt.ftypes = {tostring,tonumber}
function imt.to16bn(n)
return string.char(math.floor(n/256))..string.char(math.floor(n%256))
end
function imt.from16bn(s)
return (string.byte(s,1,1)*256)+string.byte(s,2,2)
end
function imt.encodePacket(...)
local tArgs = {...}
local packet = string.char(#tArgs%256)
for _,segment in ipairs(tArgs) do
local segtype = type(segment)
segment = tostring(segment)
packet = packet .. imt.to16bn(segment:len()) .. string.char(imt.ttypes[segtype]) .. tostring(segment)
end
packet = imt.to16bn(packet:len()) .. packet
return packet
end
function imt.decodePacket(s)
local count=0
local function getfirst(n)
local ns = s:sub(1,n)
s=s:sub(n+1)
count=count+n
return ns
end
if s:len() < 2 then return false end
local plen = imt.from16bn(getfirst(2))
local segments, starts = {}, {}
if s:len() < plen then return false end
local nsegments = string.byte(getfirst(1))
-- print(tostring(plen).." bytes, "..tostring(nsegments).." segments")
for i = 1, nsegments do
starts[#starts+1] = count+3
local seglen = imt.from16bn(getfirst(2))
local segtype = imt.ftypes[string.byte(getfirst(1))]
local segment = getfirst(seglen)
-- print(seglen,segtype,segment,type(segment))
segments[#segments+1] = segment
end
return segments, starts
end
function imt.getRemainder(s)
local function getfirst(n)
local ns = s:sub(1,n)
s=s:sub(n+1)
return ns
end
local plen = imt.from16bn(getfirst(2))
if s:len() > plen then
getfirst(plen)
return s
end
return nil
end
return imt

View File

@ -1,9 +1,10 @@
local socket = require "socket" local socket = require "socket"
local imt = require "interminitel" local imt = require "interminitel"
local pcap = require "pcap"
local tArgs = {...} local tArgs = {...}
local port, timeout = tonumber(tArgs[1]) or 4096, tonumber(tArgs[2]) or 60 local port, timeout, pf = tonumber(tArgs[1]) or 4096, tonumber(tArgs[2]) or 60, tArgs[3]
local clients, coroutines, messages = {}, {}, {} local clients, coroutines, messages = {}, {}, {}
@ -96,8 +97,18 @@ function pushLoop()
for id,msg in pairs(messages) do for id,msg in pairs(messages) do
if msg[1]:len() > 3 then if msg[1]:len() > 3 then
for k,client in pairs(clients) do for k,client in pairs(clients) do
client.conn:send(msg[1]) if k ~= msg[2] then
reprint("Message #"..tostring(id).." (Client #"..tostring(msg[2]).." -> Client #"..tostring(k).." - "..msg[1]) client.conn:send(msg[1])
reprint("Message #"..tostring(id).." (Client #"..tostring(msg[2]).." -> Client #"..tostring(k)..") - "..msg[1])
end
end
if pf then
local f=io.open(pf,"ab")
if f then
f:write(pcap.packet(msg[1]))
print(pcap.packet(msg[1]))
f:close()
end
end end
end end
messages[id] = nil messages[id] = nil
@ -124,6 +135,16 @@ end
spawn(bufferLoop) spawn(bufferLoop)
if pf then
local f=io.open(pf,"wb")
if f then
f:write(pcap.header())
f:close()
else
pf=nil
end
end
while #coroutines > 0 do while #coroutines > 0 do
for k,v in pairs(coroutines) do for k,v in pairs(coroutines) do
coroutine.resume(v) coroutine.resume(v)

35
vTunnel/pcap.lua Normal file
View File

@ -0,0 +1,35 @@
local pcap = {}
local function uint32(n)
s = ""
for i = 3, 0, -1 do
s=s..string.char((n>>(8*i))%256)
end
return s
end
local function uint16(n)
s = ""
for i = 1, 0, -1 do
s=s..string.char((n>>(8*i))%256)
end
return s
end
function pcap.header(type)
local s=uint32(0xa1b2c3d4) -- magic number
s=s..uint16(2) -- major version
s=s..uint16(4) -- minor version
s=s..uint32(0) -- timezone
s=s..uint32(0) -- accuracy
s=s..uint32(2^16) -- snaplen
s=s..uint32(type or 147)
return s
end
function pcap.packet(d)
local s = uint32(os.time()) -- timestamp
s=s..uint32(0) -- usec
s=s..uint32(d:len()) -- included length
s=s..uint32(d:len()) -- actual length
return s..d
end
return pcap