Compare commits

...

5 Commits

8 changed files with 400 additions and 149 deletions

View File

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

View File

@ -1,88 +1,142 @@
local vcomp = require "vcomponent"
local vcomponent = require "vcomponent"
local serial = require "serialization"
local component = require "component"
local computer = require "computer"
local imt = require "interminitel"
local event = require "event"
local internet = component.internet
local imt = require "interminitel"
local addr, raddr = vcomp.uuid(),vcomp.uuid()
local poll = 0.5
local listener, timer
local socket
local cfg = {}
cfg.peers = {}
cfg.rtimer = 5
cfg.katimer = 30
local listeners = {}
local timers = {}
local proxies = {}
-- dumb keepalive stuff
local keepalive = 30
local katimer
function start(faddr)
if listener then return end
if faddr then
local host,nport = faddr:match("(.+):(%d+)")
local function loadcfg()
local f = io.open("/etc/vtunnel.cfg","rb")
if not f then return false end
for k,v in pairs(serial.unserialize(f:read("*a")) or {}) do
cfg[k] = v
end
local iaddr,port = host or faddr or "shadowkat.net", tonumber(nport) or 4096
socket = internet.connect(iaddr,port)
print("Connecting to "..iaddr..":"..tostring(port).."...")
repeat
os.sleep(0.5)
until socket.finishConnect()
print("Connected!")
local proxy = {}
local rbuffer = ""
local timer = nil
function listener(t)
rbuffer=rbuffer..(socket.read(4096) or "")
if imt.decodePacket(rbuffer) then
computer.pushSignal("modem_message",addr,raddr,0,0,imt.decodePacket(rbuffer))
rbuffer = imt.getRemainder(rbuffer) or ""
end
if t == "internet_ready" and timer then
event.cancel(timer)
timer = nil
end
f:close()
end
local function savecfg()
local f = io.open("/etc/vtunnel.cfg","wb")
if not f then
print("Warning: unable to save configuration.")
return false
end
f:write(serial.serialize(cfg))
f:close()
end
local function createTunnel(host,port,addr,raddr)
local proxy = {address=addr,buffer=""}
function proxy.connect()
if proxy.socket then
proxy.socket.close()
end
proxy.socket = component.internet.connect(host,port)
local st = computer.uptime()
repeat
coroutine.yield()
until proxy.socket.finishConnect() or computer.uptime() > st+5
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(...)
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
function proxy.maxPacketSize()
return 12288
function proxy.read()
local rb, r
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
proxy.type = "tunnel"
proxy.slot = 4096
local docs = {
send = "function(data...) -- Sends the specified data to the card this one is linked to.",
maxPacketSize = "function():number -- Gets the maximum packet size (config setting)."
}
vcomp.register(addr,"tunnel",proxy,docs)
event.listen("internet_ready",proxy.read)
listeners[addr] = {"internet_ready",proxy.read}
timers[addr] = event.timer(cfg.rtimer, proxy.read, math.huge)
proxy.connect()
proxy.last = computer.uptime()
return proxy
end
function stop()
if listener then
event.ignore("internet_ready",listener)
listener = nil
function start()
loadcfg()
for k,v in pairs(cfg.peers) do
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
if timer then
event.cancel(timer)
timer = nil
end
if katimer then
event.cancel(katimer)
katimer = nil
end
if component.type(addr) then
vcomp.unregister(addr)
end
socket.close()
end
function stop()
for k,v in pairs(listeners) do
event.ignore(v[1],v[2])
end
for k,v in pairs(timers) do
event.cancel(v)
end
for k,v in pairs(proxies) do
vcomponent.unregister(k)
end
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

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 imt = require "interminitel"
local pcap = require "pcap"
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 = {}, {}, {}
@ -96,8 +97,18 @@ function pushLoop()
for id,msg in pairs(messages) do
if msg[1]:len() > 3 then
for k,client in pairs(clients) do
client.conn:send(msg[1])
reprint("Message #"..tostring(id).." (Client #"..tostring(msg[2]).." -> Client #"..tostring(k).." - "..msg[1])
if k ~= msg[2] then
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
messages[id] = nil
@ -124,6 +135,16 @@ end
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
for k,v in pairs(coroutines) do
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