Skip to content
Browse files

The "wtf it actually works?" release

  • Loading branch information...
0 parents commit 4bb828fe51097e1b829b8946d1e712f27098d76a @msantos committed
Showing with 1,022 additions and 0 deletions.
  1. +2 −0 .gitignore
  2. +23 −0 Makefile
  3. +61 −0 README
  4. +19 −0 ebin/evum.app
  5. +3 −0 include/evum.hrl
  6. +4 −0 priv/linux
  7. +5 −0 rebar.config
  8. +256 −0 src/evum.erl
  9. +177 −0 src/evum_ctl.erl
  10. +209 −0 src/evum_data.erl
  11. +164 −0 src/evum_mcons.erl
  12. +96 −0 src/mcons.erl
  13. +3 −0 start.sh
2 .gitignore
@@ -0,0 +1,2 @@
+*.[oa]
+*.beam
23 Makefile
@@ -0,0 +1,23 @@
+
+REBAR=$(shell which rebar || echo ./rebar)
+
+all: dirs deps compile
+
+./rebar:
+ erl -noshell -s inets start \
+ -eval 'httpc:request(get, {"https://github.com/downloads/basho/rebar/rebar", []}, [], [{stream, "./rebar"}])' \
+ -s init stop
+ chmod +x ./rebar
+
+dirs:
+ @mkdir -p priv/tmp priv/uml
+
+compile: $(REBAR)
+ @$(REBAR) compile
+
+clean: $(REBAR)
+ @$(REBAR) clean
+
+deps: $(REBAR)
+ @$(REBAR) get-deps
+
61 README
@@ -0,0 +1,61 @@
+
+Spawn a Linux VM as an Erlang process in the beam VM.
+
+
+EXAMPLE
+
+1> {ok,R} = evum:start().
+{ok,<0.43.0>}
+
+2> flush(). % See the Linux boot messages
+
+3> evum:ifconfig(R, {"192.168.213.92","255.255.255.0","192.168.213.1"}).
+
+4> evum:send(R, "ifconfig -a").
+<<"root@(none):/# ifconfig -a">>
+
+5> flush().
+Shell got <<"eth0 Link encap:Ethernet HWaddr 00:21:5d:7c:11:11 ">>
+Shell got <<" inet addr:192.168.213.92 Bcast:192.168.213.255 Mask:255.255.255.0">>
+Shell got <<" UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1">>
+Shell got <<" RX packets:0 errors:0 dropped:0 overruns:0 frame:0">>
+Shell got <<" TX packets:0 errors:0 dropped:0 overruns:0 carrier:0">>
+Shell got <<" collisions:0 txqueuelen:1000 ">>
+Shell got <<" RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)">>
+Shell got <<" Interrupt:5 ">>
+Shell got <<>>
+Shell got <<"lo Link encap:Local Loopback ">>
+Shell got <<" LOOPBACK MTU:16436 Metric:1">>
+Shell got <<" RX packets:0 errors:0 dropped:0 overruns:0 frame:0">>
+Shell got <<" TX packets:0 errors:0 dropped:0 overruns:0 carrier:0">>
+Shell got <<" collisions:0 txqueuelen:0 ">>
+Shell got <<" RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)">>
+Shell got <<>>
+
+6> evum:ping(R, {192,168,213,7}).
+<<"root@(none):/# ping -c 1 192.168.213.7">>
+
+7> flush().
+Shell got <<"PING 192.168.213.7 (192.168.213.7) 56(84) bytes of data.">>
+Shell got <<"64 bytes from 192.168.213.7: icmp_seq=1 ttl=64 time=53.9 ms">>
+Shell got <<>>
+Shell got <<"--- 192.168.213.7 ping statistics ---">>
+Shell got <<"1 packets transmitted, 1 received, 0% packet loss, time 0ms">>
+Shell got <<"rtt min/avg/max/mdev = 53.918/53.918/53.918/0.000 ms">>
+
+% start up a shell (of sorts) on a port
+10> evum:send(R, "socat tcp-l:7777,reuseaddr,fork system:'/bin/bash',stderr").
+<<"root@(none):/# socat tcp-l:7777,reuseaddr,fork system:'/bin/bash',stderr">>
+
+From another host:
+
+$ nc 192.168.213.92 7777
+cat /proc/cpuinfo
+processor : 0
+vendor_id : User Mode Linux
+model name : UML
+mode : skas
+host : Linux rst 2.6.32-26-generic #48-Ubuntu SMP Wed Nov 24 09:00:03 UTC 2010 i686
+bogomips : 1468.00
+
+
19 ebin/evum.app
@@ -0,0 +1,19 @@
+{application, evum,
+ [
+ {description, "Linux VM in an Erlang VM"},
+ {vsn, "0.01"},
+ {modules, [
+ evum,
+ evum_mcons,
+ evum_ctl,
+ evum_data,
+ mcons
+ ]},
+ {registered, []},
+ {applications, [
+ kernel,
+ stdlib
+ ]},
+ {env, []}
+ ]}.
+
3 include/evum.hrl
@@ -0,0 +1,3 @@
+
+-define(UML_DIR, "priv/uml").
+
4 priv/linux
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+linux $@
+
5 rebar.config
@@ -0,0 +1,5 @@
+{deps, [
+ {epcap, ".*", {git, "git://github.com/msantos/epcap.git", "master"}},
+ {procket, ".*", {git, "git://github.com/msantos/procket.git", "master"}}
+ ]}.
+{erl_opts, [{i, "deps/epcap/include"}, {i, "deps/procket/include"}]}.
256 src/evum.erl
@@ -0,0 +1,256 @@
+%% Copyright (c) 2010, Michael Santos <michael.santos@gmail.com>
+%% All rights reserved.
+%%
+%% Redistribution and use in source and binary forms, with or without
+%% modification, are permitted provided that the following conditions
+%% are met:
+%%
+%% Redistributions of source code must retain the above copyright
+%% notice, this list of conditions and the following disclaimer.
+%%
+%% Redistributions in binary form must reproduce the above copyright
+%% notice, this list of conditions and the following disclaimer in the
+%% documentation and/or other materials provided with the distribution.
+%%
+%% Neither the name of the author nor the names of its contributors
+%% may be used to endorse or promote products derived from this software
+%% without specific prior written permission.
+%%
+%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+%% "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+%% LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+%% FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+%% COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+%% INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+%% BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+%% LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+%% CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+%% LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+%% ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+%% POSSIBILITY OF SUCH DAMAGE.
+-module(evum).
+-behaviour(gen_server).
+
+-include("evum.hrl").
+
+-define(SERVER, ?MODULE).
+-define(PROGNAME, "priv/linux").
+
+-export([ping/2, sniff/1, ifconfig/2]).
+-export([start/0, start/1, start/2, stop/1]).
+-export([send/2, send/3, puts/2, system/2, reboot/1]).
+-export([umid/1, make_args/1, defaults/0]).
+-export([start_link/2]).
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+-record(state, {
+ uml_dir = ?UML_DIR,
+ cons,
+ pid,
+ port
+}).
+
+-define(DEFAULT_BOOT_OPTIONS, [
+ net,
+ {mem, 64},
+ {rootfstype, "hostfs"},
+ {verbose, false},
+ write,
+ {init, "/bin/bash"}
+ ]).
+
+
+ifconfig(Ref, {IP, Netmask, Default}) ->
+ send(Ref, "ifconfig eth0 " ++ IP ++ " netmask " ++ Netmask),
+ send(Ref, "route add default gw " ++ Default).
+
+ping(Ref, {A,B,C,D} = IP) when is_integer(A), is_integer(B), is_integer(C), is_integer(D) ->
+ send(Ref, "ping -c 1 " ++ inet_parse:ntoa(IP)).
+
+sniff(Ref) ->
+ send(Ref, "tcpdump -s 0 -w /tmp/sniff.out -i eth0 > /dev/null 2>&1 &").
+
+
+defaults() -> ?DEFAULT_BOOT_OPTIONS.
+
+start() ->
+ Pid = self(),
+ start_link(Pid, defaults()).
+start(Options) ->
+ start_link(self(), Options).
+start(Pid, Options) when is_pid(Pid), is_list(Options) ->
+ start_link(Pid, Options).
+
+stop(Ref) ->
+ gen_server:call(Ref, stop).
+
+reboot(Ref) ->
+ system(Ref, <<"reboot">>).
+
+system(Ref, Command) when is_list(Command) ->
+ system(Ref, list_to_binary(Command));
+system(Ref, Command) when is_binary(Command) ->
+ gen_server:call(Ref, {system, Command}, infinity).
+
+send(Ref, Data) when is_list(Data); is_binary(Data) ->
+ send(Ref, Data, infinity).
+send(Ref, Data, Timeout) when is_list(Data); is_binary(Data) ->
+ case gen_server:call(Ref, {send, list_to_binary([Data, "\n"])}, infinity) of
+ ok ->
+ receive
+ N -> N
+ after
+ Timeout -> timeout
+ end;
+ Error -> Error
+ end.
+
+puts(Ref, Data) when is_list(Data); is_binary(Data) ->
+ gen_server:call(Ref, {send, Data}, infinity).
+
+start_link(Pid, Options) ->
+ {ok, Ref} = gen_server:start_link(?MODULE, [Pid, Options], []),
+ boot(Ref, Options),
+ {ok,Ref}.
+
+init([Pid, Options]) ->
+ process_flag(trap_exit, true),
+ Default = #state{},
+ UmlId = umid(Pid),
+
+ % Start network
+ {ok,_} = evum_data:start(),
+ {ok,_} = evum_ctl:start(),
+
+ Args = case proplists:get_value(net, Options, false) of
+ false -> Options;
+ true ->
+ [Dev] = packet:default_interface(),
+ {ok,[{hwaddr, HW}]} = inet:ifget(Dev, [hwaddr]),
+ MAC = lists:flatten(string:join([ io_lib:format("~.16B", [N]) || N <- HW ], ":")),
+ [{eth, 0, ?UML_DIR ++ "/evum.ctl", MAC}] ++ Options
+ end,
+ Cmd = make_args([
+ {umid, UmlId},
+ {uml_dir, Default#state.uml_dir}
+ ] ++ Args),
+ Port = open_port({spawn, Cmd}, [
+ {line, 2048},
+ binary,
+ stderr_to_stdout,
+ exit_status
+ ]),
+ {ok, Cons} = evum_mcons:open(Default#state.uml_dir ++ "/" ++
+ UmlId ++ "/mconsole"),
+ {ok, #state{
+ cons = Cons,
+ pid = Pid,
+ port = Port
+ }}.
+
+
+handle_call({send, Data}, {Pid,_}, #state{pid = Pid, port = Port} = State) ->
+ Reply = case erlang:port_command(Port, Data) of
+ true -> ok;
+ Error -> Error
+ end,
+ {reply, Reply, State};
+
+handle_call({system, Command}, {Pid,_}, #state{pid = Pid, cons = Cons} = State) ->
+ Reply = evum_mcons:send(Cons, Command),
+ {reply, Reply, State};
+handle_call(stop, {Pid,_}, #state{pid = Pid} = State) ->
+ {stop, normal, ok, State}.
+
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+terminate(_Reason, #state{cons = Cons, port = Port}) ->
+ ok = evum_mcons:send(Cons, <<"halt">>),
+ error_logger:info_report([{terminate, closed}]),
+ evum_mcons:close(Cons),
+ try erlang:port_close(Port) of
+ true -> ok
+ catch
+ _:_ -> ok
+ end.
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+
+%%--------------------------------------------------------------------
+%%% Port communication
+%%--------------------------------------------------------------------
+handle_info({Port, {data, {Eol, Data}}}, #state{port = Port, pid = Pid} = State) when Eol == eol; Eol == noeol ->
+ Pid ! Data,
+ {noreply, State};
+
+handle_info({Port, {exit_status, Status}}, #state{port = Port} = State) when Status > 128 ->
+ {stop, {port_terminated, Status-128}, State};
+handle_info({Port, {exit_status, Status}}, #state{port = Port} = State) ->
+ {stop, {port_terminated, Status}, #state{port = Port} = State};
+handle_info({'EXIT', Port, Reason}, #state{port = Port} = State) ->
+ {stop, {shutdown, Reason}, State};
+
+% WTF
+handle_info(Info, State) ->
+ error_logger:error_report([{wtf, Info}]),
+ {noreply, State}.
+
+
+%%--------------------------------------------------------------------
+%%% Boot up
+%%--------------------------------------------------------------------
+boot(Ref, _Options) ->
+ send(Ref, <<"mount -t proc /proc /proc">>),
+ ok.
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+make_args(PL) ->
+ proplists:get_value(progname, PL, ?PROGNAME) ++ " " ++
+ string:join([ get_switch(proplists:lookup(Arg, PL)) || Arg <- [
+ disk,
+ eth,
+ init,
+ mem,
+ rootfstype,
+ umid,
+ uml_dir,
+ write
+ ], proplists:lookup(Arg, PL) /= none ],
+ " ").
+
+get_switch({disk, Dev, Image}) -> Dev ++ "=" ++ Image;
+get_switch({disk, Dev, Image, Cow}) -> Dev ++ "=" ++ Cow ++ "," ++ Image;
+
+% eth[0-9]+=daemon,ethernet_address,type,control_socket,data_socket
+% eth0=daemon,,unix,/var/run/uml-utilities/uml_switch.ctl
+get_switch({eth, Dev, Path}) -> get_switch({eth, Dev, Path, ""});
+get_switch({eth, Dev, Path, MAC}) ->
+ "eth" ++ integer_to_list(Dev) ++ "=daemon," ++ MAC ++ ",unix," ++ Path;
+
+get_switch({init, Arg}) -> "init=" ++ Arg;
+
+get_switch({mem, Arg}) when is_integer(Arg) -> "mem=" ++ integer_to_list(Arg) ++ "M";
+get_switch({mem, Arg}) when is_list(Arg) -> "mem=" ++ Arg;
+
+get_switch({rootfstype, Arg}) -> "rootfstype=" ++ Arg;
+
+get_switch({umid, Arg}) -> "umid=" ++ Arg;
+
+get_switch({uml_dir, Arg}) -> "uml_dir=" ++ Arg;
+
+get_switch({verbose, true}) -> "";
+get_switch({verbose, false}) -> "quiet";
+
+get_switch({write, true}) -> "rw".
+
+
+umid(Pid) when is_pid(Pid) ->
+ {match,[A,B,C]} = re:run(pid_to_list(Pid), "<(\\d+)\.(\\d+)\.(\\d+)>", [{capture, [1,2,3], list}]),
+ "pid-" ++ os:getpid() ++ "-" ++ A ++ "-" ++ B ++ "-" ++ C.
+
+
177 src/evum_ctl.erl
@@ -0,0 +1,177 @@
+%% Copyright (c) 2010, Michael Santos <michael.santos@gmail.com>
+%% All rights reserved.
+%%
+%% Redistribution and use in source and binary forms, with or without
+%% modification, are permitted provided that the following conditions
+%% are met:
+%%
+%% Redistributions of source code must retain the above copyright
+%% notice, this list of conditions and the following disclaimer.
+%%
+%% Redistributions in binary form must reproduce the above copyright
+%% notice, this list of conditions and the following disclaimer in the
+%% documentation and/or other materials provided with the distribution.
+%%
+%% Neither the name of the author nor the names of its contributors
+%% may be used to endorse or promote products derived from this software
+%% without specific prior written permission.
+%%
+%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+%% "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+%% LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+%% FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+%% COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+%% INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+%% BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+%% LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+%% CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+%% LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+%% ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+%% POSSIBILITY OF SUCH DAMAGE.
+-module(evum_ctl).
+-behaviour(gen_server).
+
+-include("procket.hrl").
+-include("evum.hrl").
+
+-export([start/0, start/1, stop/1]).
+-export([data/3]).
+-export([start_link/1]).
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+-define(SERVER, ?MODULE).
+
+-define(SWITCH_MAGIC, 16#feedface).
+-define(UML_VERSION, 3).
+-define(REQ_NEW_CONTROL, 0).
+
+-record(state, {
+ uml_dir = ?UML_DIR,
+ pid,
+ path,
+ sun,
+ s
+}).
+
+
+start() ->
+ start_link(self() ).
+start(Pid) when is_pid(Pid) ->
+ start_link(Pid).
+
+stop(Ref) ->
+ gen_server:call(Ref, stop).
+
+data(Ref, Socket, Data) ->
+ gen_server:call(Ref, {data, Socket, Data}).
+
+start_link(Pid) ->
+ gen_server:start_link(?MODULE, [Pid], []).
+
+init([Pid]) ->
+ process_flag(trap_exit, true),
+ Path = ?UML_DIR ++ "/evum.ctl",
+
+ {ok, Socket} = procket:socket(?PF_LOCAL, ?SOCK_STREAM, 0),
+ ok = procket:setsockopt(Socket, ?SOL_SOCKET, ?SO_REUSEADDR, <<1:32/native>>),
+
+ Sun = list_to_binary([
+ <<?PF_LOCAL:16/native>>,
+ Path,
+ <<0:((?UNIX_PATH_MAX-length(Path))*8)>>
+ ]),
+
+ ok = procket:bind(Socket, Sun),
+ ok = procket:listen(Socket, 5),
+
+ Server = self(),
+ spawn_link(fun() -> accept(Server, Socket) end),
+
+ {ok, #state{
+ path = Path,
+ s = Socket,
+ sun = Sun,
+ pid = Pid
+ }}.
+
+
+handle_call({data, Socket, Data}, _From, #state{s = _Socket, sun = _Sun} = State) ->
+ error_logger:info_report({ctl, Socket, Data}),
+ {reply, ok, State};
+
+handle_call(stop, {Pid,_}, #state{pid = Pid} = State) ->
+ {stop, shutdown, ok, State}.
+
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+terminate(_Reason, #state{s = Socket, path = Path}) ->
+ procket:close(Socket),
+ file:delete(Path).
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+
+%%--------------------------------------------------------------------
+%% Stream data from the ctl socket
+%%--------------------------------------------------------------------
+handle_info(Info, State) ->
+ error_logger:error_report([{wtf, Info}]),
+ {noreply, State}.
+
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+
+% XXX this will leak the listener and accepted fd's
+accept(Server, Listen) ->
+ case procket:accept(Listen) of
+ {error, eagain} ->
+ timer:sleep(1000);
+ {ok, Socket} ->
+ spawn(fun() -> recv(Server, Socket) end)
+ end,
+ accept(Server, Listen).
+
+% struct request_v3 {
+% uint32_t magic;
+% uint32_t version;
+% enum request_type type;
+% struct sockaddr_un sock;
+% };
+recv(Server, Socket) ->
+ case procket:recvfrom(Socket, 16#FFFF) of
+ eagain ->
+ timer:sleep(10),
+ recv(Server, Socket);
+ {ok, <<>>} ->
+ procket:close(Socket);
+ {ok, <<?SWITCH_MAGIC:32/native, ?UML_VERSION:32/native, ?REQ_NEW_CONTROL:32/native, Sun/binary>>} ->
+ error_logger:info_report([{connect_from, Sun}]),
+ Data = evum_data:name(),
+ case procket:sendto(Socket, Data, 0, <<>>) of
+ ok ->
+% data(Server, Socket, Buf),
+ recv(Server, Socket);
+ _ ->
+ procket:close(Socket)
+ end;
+ % Unknown request
+ {ok, <<Magic:32/native, Version:32/native, Type:32/native, Sun/binary>>} ->
+ error_logger:info_report([
+ {unknown_request, evum_ctl},
+ {magic, Magic},
+ {magic, Magic},
+ {version, Version},
+ {type, Type},
+ {sun, Sun}
+ ]),
+ procket:close(Socket);
+ Error ->
+ error_logger:info_report([{error, Error}]),
+ procket:close(Socket)
+ end.
+
+
209 src/evum_data.erl
@@ -0,0 +1,209 @@
+%% Copyright (c) 2010, Michael Santos <michael.santos@gmail.com>
+%% All rights reserved.
+%%
+%% Redistribution and use in source and binary forms, with or without
+%% modification, are permitted provided that the following conditions
+%% are met:
+%%
+%% Redistributions of source code must retain the above copyright
+%% notice, this list of conditions and the following disclaimer.
+%%
+%% Redistributions in binary form must reproduce the above copyright
+%% notice, this list of conditions and the following disclaimer in the
+%% documentation and/or other materials provided with the distribution.
+%%
+%% Neither the name of the author nor the names of its contributors
+%% may be used to endorse or promote products derived from this software
+%% without specific prior written permission.
+%%
+%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+%% "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+%% LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+%% FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+%% COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+%% INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+%% BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+%% LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+%% CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+%% LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+%% ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+%% POSSIBILITY OF SUCH DAMAGE.
+-module(evum_data).
+-behaviour(gen_server).
+
+-include("epcap_net.hrl").
+-include("procket.hrl").
+-include("evum.hrl").
+
+-export([start/0, start/1, stop/1]).
+-export([name/0,data/2]).
+-export([start_link/1]).
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+-define(SERVER, ?MODULE).
+-define(ETH_ALEN, 6).
+
+-record(state, {
+ uml_dir = ?UML_DIR,
+ mac,
+ ip,
+ arp,
+ ifindex,
+ pid,
+ sun,
+ s
+ }).
+
+
+start() ->
+ start_link(self()).
+start(Pid) when is_pid(Pid) ->
+ start_link(Pid).
+
+stop(Ref) ->
+ gen_server:call(Ref, stop).
+
+name() ->
+ gen_server:call(?SERVER, name).
+
+data(net, Data) ->
+ gen_server:call(?SERVER, {net, Data});
+data(unix, {Sun, Data}) ->
+ gen_server:call(?SERVER, {unix, Sun, Data}).
+
+start_link(Pid) ->
+ gen_server:start_link({local, ?SERVER}, ?MODULE, [Pid], []).
+
+init([Pid]) ->
+ process_flag(trap_exit, true),
+
+ {ok, Socket} = procket:socket(?PF_LOCAL, ?SOCK_DGRAM, 0),
+
+ %struct {
+ % char zero;
+ % int pid;
+ % int usecs;
+ %} name;
+ Proc = list_to_integer(os:getpid()),
+ {_,_,USecs} = erlang:now(),
+
+ Sun = <<
+ ?PF_LOCAL:16/native,
+ 0:8,
+ Proc:32/native,
+ USecs:32/native,
+ 0:((?UNIX_PATH_MAX-9)*8)
+ >>,
+ ok = procket:bind(Socket, Sun),
+
+ % Raw socket
+ {ok, IP} = packet:socket(),
+ {ok, ARP} = packet:socket(?ETH_P_ARP),
+
+ [If] = packet:default_interface(),
+ Ifindex = packet:ifindex(IP, If),
+
+ State = #state{
+ mac = gb_trees:empty(),
+ ip = IP,
+ arp = ARP,
+ ifindex = Ifindex,
+ s = Socket,
+ sun = Sun,
+ pid = Pid
+ },
+
+ % Monitor the network for IP events
+ spawn_link(fun() -> net(IP) end),
+
+ % Monitor the network for ARP events
+ spawn_link(fun() -> net(ARP) end),
+
+ % Monitor the Unix socket for events
+ spawn_link(fun() -> unix(Socket) end),
+
+ {ok, State}.
+
+
+handle_call(name, _From, #state{sun = Sun} = State) ->
+ {reply, Sun, State};
+
+handle_call({net, Data}, _From, #state{s = Socket, mac = MAC} = State) ->
+ {#ether{dhost = Dhost, type = Type}, _Packet} = epcap_net:ether(Data),
+ % pretend we're switched
+ case gb_trees:lookup(Dhost, MAC) of
+ none when Type == ?ETH_P_ARP ->
+ [ ok = procket:sendto(Socket, Data, 0, Sun) || {_,Sun} <- gb_trees:to_list(MAC) ];
+ none when Dhost == <<255,255,255,255>> ->
+ [ ok = procket:sendto(Socket, Data, 0, Sun) || {_,Sun} <- gb_trees:to_list(MAC) ];
+ none ->
+ ok;
+ {value, Sun} ->
+ procket:sendto(Socket, Data, 0, Sun)
+ end,
+ % pretend we're hubbed
+% [ ok = procket:sendto(Socket, Data, 0, Sun) || {_,Sun} <- gb_trees:to_list(MAC) ],
+ {reply, ok, State};
+
+handle_call({unix, Sun, Data}, _From, #state{ip = IP, arp = ARP, ifindex = Ifindex, mac = MAC} = State) ->
+ {#ether{shost = Ether, type = Type}, _Packet} = epcap_net:ether(Data),
+ Socket = case Type of
+ ?ETH_P_ARP -> ARP;
+ ?ETH_P_IP -> IP
+ end,
+ ok = packet:send(Socket, Ifindex, Data),
+ {reply, ok, State#state{mac = gb_trees:enter(Ether, Sun, MAC)}};
+
+handle_call(stop, {Pid,_}, #state{pid = Pid} = State) ->
+ {stop, shutdown, ok, State}.
+
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+
+terminate(_Reason, #state{s = Socket}) ->
+ % abstract Unix socket, no file to unlink
+ procket:close(Socket),
+ ok.
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+handle_info(Info, State) ->
+ error_logger:error_report([{wtf, Info}]),
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+net(Socket) ->
+ case procket:recvfrom(Socket, 65535) of
+ eagain ->
+ timer:sleep(10),
+ net(Socket);
+ {ok, Buf} ->
+ data(net, Buf),
+ net(Socket);
+ Error ->
+ error_logger:error_report(Error),
+ procket:close(Socket)
+ end.
+
+
+unix(Socket) ->
+ case procket:recvfrom(Socket, 65535, 0, 110) of
+ eagain ->
+ timer:sleep(10),
+ unix(Socket);
+ {ok, Buf, Sun} ->
+ data(unix, {Sun, Buf}),
+ unix(Socket);
+ Error ->
+ error_logger:error_report(Error),
+ procket:close(Socket)
+ end.
+
+
164 src/evum_mcons.erl
@@ -0,0 +1,164 @@
+%% Copyright (c) 2010, Michael Santos <michael.santos@gmail.com>
+%% All rights reserved.
+%%
+%% Redistribution and use in source and binary forms, with or without
+%% modification, are permitted provided that the following conditions
+%% are met:
+%%
+%% Redistributions of source code must retain the above copyright
+%% notice, this list of conditions and the following disclaimer.
+%%
+%% Redistributions in binary form must reproduce the above copyright
+%% notice, this list of conditions and the following disclaimer in the
+%% documentation and/or other materials provided with the distribution.
+%%
+%% Neither the name of the author nor the names of its contributors
+%% may be used to endorse or promote products derived from this software
+%% without specific prior written permission.
+%%
+%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+%% "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+%% LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+%% FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+%% COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+%% INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+%% BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+%% LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+%% CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+%% LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+%% ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+%% POSSIBILITY OF SUCH DAMAGE.
+-module(evum_mcons).
+-include("procket.hrl").
+-include("evum.hrl").
+-export([
+ open/0, open/1, close/1,
+ help/1,
+ send/2, send/3,
+ cmd/1, response/1,
+ write/2, read/1
+ ]).
+
+-define(MCONSOLE_MAGIC, 16#cafebabe).
+-define(MCONSOLE_MAX_DATA, 512).
+-define(MCONSOLE_VERSION, 2).
+
+-record(socket, {
+ s,
+ sun
+ }).
+
+
+open() ->
+ case filelib:wildcard("*/mconsole", ?UML_DIR) of
+ [] -> none;
+ [Console|_] -> open(?UML_DIR ++ "/" ++ Console)
+ end.
+
+open(Path) when is_list(Path) ->
+ open(list_to_binary(Path));
+open(Path) when is_binary(Path), byte_size(Path) < ?UNIX_PATH_MAX ->
+ {ok, Socket} = procket:socket(?PF_LOCAL, ?SOCK_DGRAM, 0),
+ Pid = list_to_binary(string:right(os:getpid(), 5)),
+ Sun = <<?PF_LOCAL:16/native, % sun_family
+ 0:8, % abstract address
+ Pid/binary, % address
+ 0:((?UNIX_PATH_MAX-(1+byte_size(Pid)))*8)
+ >>,
+ ok = procket:bind(Socket, Sun),
+ {ok, #socket{
+ s = Socket,
+ sun = <<?PF_LOCAL:16/native,
+ Path/binary,
+ 0:((?UNIX_PATH_MAX-byte_size(Path))*8)
+ >>
+ }}.
+
+close(#socket{s = Socket}) ->
+ procket:close(Socket).
+
+help(Socket) ->
+ {0, Output} = send(Socket, <<"help">>),
+ io:format("~s", [Output]).
+
+
+% version - Get kernel version
+% help - Print this message
+% halt - Halt UML
+% reboot - Reboot UML
+% config <dev>=<config> - Add a new device to UML;
+% same syntax as command line
+% config <dev> - Query the configuration of a device
+% remove <dev> - Remove a device from UML
+% sysrq <letter> - Performs the SysRq action controlled by the letter
+% cad - invoke the Ctrl-Alt-Del handler
+% stop - pause the UML; it will do nothing until it receives a 'go'
+% go - continue the UML after a 'stop'
+% log <string> - make UML enter <string> into the kernel log
+% proc <file> - returns the contents of the UML's /proc/<file>
+send(Socket, Command) ->
+ send(Socket, Command, []).
+send(Socket, Command, []) when is_list(Command) ->
+ send(Socket, list_to_binary(Command), []);
+send(#socket{} = Socket, Command, []) when is_binary(Command) ->
+ ok = write(Socket, cmd(Command)),
+ recv(Socket).
+
+
+recv(Socket) ->
+ recv(Socket, []).
+recv(Socket, Acc) ->
+ {ok, Response} = read(Socket),
+ case response(Response) of
+ {0, 0, _Len, <<0>>} ->
+ ok;
+ {0, 0, _Len, Data} ->
+ {ok, list_to_binary(lists:reverse([binary:part(Data,0,byte_size(Data)-1)|Acc]))};
+ {_Err, 0, _Len, Data} ->
+ {error, list_to_binary(lists:reverse([binary:part(Data,0,byte_size(Data)-1)|Acc]))};
+ {_Err, _More, _Len, Data} ->
+ recv(Socket, [binary:part(Data,0,byte_size(Data)-1)|Acc])
+ end.
+
+
+write(#socket{s = FD, sun = Sun}, Cmd) when is_binary(Cmd) ->
+ procket:sendto(FD, Cmd, 0, Sun).
+
+read(Socket) ->
+ read(Socket, 0).
+read(#socket{s = FD} = Socket, X) ->
+ case procket:recvfrom(FD, 4+4+4+?MCONSOLE_MAX_DATA) of
+ eagain when X < 10 -> timer:sleep(10), read(Socket, X+1);
+ eagain -> eagain;
+ {ok, _} = N -> N
+ end.
+
+
+% struct mconsole_request {
+% uint32_t magic;
+% uint32_t version;
+% uint32_t len;
+% char data[MCONSOLE_MAX_DATA];
+% };
+cmd(Cmd) when byte_size(Cmd) < 4+4+4+?MCONSOLE_MAX_DATA ->
+ Size = byte_size(Cmd),
+
+ <<?MCONSOLE_MAGIC:32/native,
+ ?MCONSOLE_VERSION:32/native,
+ Size:32/native,
+ Cmd/bytes,
+ 0:((?MCONSOLE_MAX_DATA-Size)*8)
+ >>.
+
+
+% struct mconsole_reply {
+% uint32_t err;
+% uint32_t more;
+% uint32_t len;
+% char data[MCONSOLE_MAX_DATA];
+% };
+response(<<Err:4/unsigned-integer-unit:8, More:4/unsigned-integer-unit:8,
+ Len:4/unsigned-integer-unit:8, Data/bytes>>) ->
+ {Err, More, Len, Data}.
+
+
96 src/mcons.erl
@@ -0,0 +1,96 @@
+%% Copyright (c) 2010, Michael Santos <michael.santos@gmail.com>
+%% All rights reserved.
+%%
+%% Redistribution and use in source and binary forms, with or without
+%% modification, are permitted provided that the following conditions
+%% are met:
+%%
+%% Redistributions of source code must retain the above copyright
+%% notice, this list of conditions and the following disclaimer.
+%%
+%% Redistributions in binary form must reproduce the above copyright
+%% notice, this list of conditions and the following disclaimer in the
+%% documentation and/or other materials provided with the distribution.
+%%
+%% Neither the name of the author nor the names of its contributors
+%% may be used to endorse or promote products derived from this software
+%% without specific prior written permission.
+%%
+%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+%% "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+%% LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+%% FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+%% COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+%% INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+%% BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+%% LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+%% CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+%% LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+%% ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+%% POSSIBILITY OF SUCH DAMAGE.
+-module(mcons).
+-include("evum.hrl").
+
+-export([
+ open/0, open/1,
+ version/1, help/1, halt/1,
+ reboot/1,
+ config/2, config/3,
+ remove/2,
+ sysrq/2, cad/1, stop/1, go/1,
+ log/2, proc/2
+ ]).
+
+% version - Get kernel version
+% help - Print this message
+% halt - Halt UML
+% reboot - Reboot UML
+% config <dev>=<config> - Add a new device to UML;
+% same syntax as command line
+% config <dev> - Query the configuration of a device
+% remove <dev> - Remove a device from UML
+% sysrq <letter> - Performs the SysRq action controlled by the letter
+% cad - invoke the Ctrl-Alt-Del handler
+% stop - pause the UML; it will do nothing until it receives a 'go'
+% go - continue the UML after a 'stop'
+% log <string> - make UML enter <string> into the kernel log
+% proc <file> - returns the contents of the UML's /proc/<file>
+
+open() ->
+ evum_mcons:open().
+open(Path) ->
+ evum_mcons:open(Path).
+
+version(Socket) ->
+ evum_mcons:send(Socket, <<"version">>).
+help(Socket) ->
+ evum_mcons:send(Socket, <<"help">>).
+halt(Socket) ->
+ evum_mcons:send(Socket, <<"halt">>).
+reboot(Socket) ->
+ evum_mcons:send(Socket, <<"reboot">>).
+
+cad(Socket) ->
+ evum_mcons:send(Socket, <<"cad">>).
+stop(Socket) ->
+ evum_mcons:send(Socket, <<"stop">>).
+go(Socket) ->
+ evum_mcons:send(Socket, <<"go">>).
+
+config(Socket, Dev) ->
+ evum_mcons:send(Socket, <<"config ", Dev>>).
+config(Socket, Dev, Config) ->
+ evum_mcons:send(Socket, <<"config ", Dev, "=", Config>>).
+
+remove(Socket, Config) ->
+ evum_mcons:send(Socket, <<"remove ", Config>>).
+
+sysrq(Socket, Config) ->
+ evum_mcons:send(Socket, <<"sysrq ", Config>>).
+
+log(Socket, Config) ->
+ evum_mcons:send(Socket, <<"log ", Config>>).
+proc(Socket, Config) ->
+ evum_mcons:send(Socket, <<"proc ">>, Config).
+
+
3 start.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+exec erl -boot start_sasl -pa $PWD/ebin $PWD/deps/*/ebin

0 comments on commit 4bb828f

Please sign in to comment.
Something went wrong with that request. Please try again.