├── .gitignore ├── Makefile ├── README.md ├── rebar.config ├── src ├── gen_unix.app.src ├── gen_unix.erl └── unixsock.erl └── test └── gen_unix_tests.erl /.gitignore: -------------------------------------------------------------------------------- 1 | *.[oa] 2 | *.beam 3 | /deps/* 4 | /.eunit/ 5 | ebin/ 6 | *.swp 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | REBAR=$(shell which rebar || echo ./rebar) 2 | DEPSOLVER_PLT=$(CURDIR)/.depsolver_plt 3 | 4 | all: dirs deps compile 5 | 6 | ./rebar: 7 | erl -noshell -s inets start -s ssl start \ 8 | -eval 'httpc:request(get, {"https://raw.github.com/wiki/rebar/rebar/rebar", []}, [], [{stream, "./rebar"}])' \ 9 | -s inets stop -s init stop 10 | chmod +x ./rebar 11 | 12 | dirs: 13 | @mkdir -p priv/tmp 14 | 15 | compile: $(REBAR) 16 | @$(REBAR) compile 17 | 18 | clean: $(REBAR) 19 | @$(REBAR) clean 20 | 21 | deps: $(REBAR) 22 | @$(REBAR) check-deps || $(REBAR) get-deps 23 | 24 | test: $(REBAR) compile 25 | @$(REBAR) xref eunit recursive=false 26 | 27 | examples: eg 28 | eg: 29 | @erlc -I deps -o ebin examples/*.erl 30 | 31 | .PHONY: test dialyzer typer clean distclean 32 | 33 | $(DEPSOLVER_PLT): 34 | @dialyzer --output_plt $(DEPSOLVER_PLT) --build_plt \ 35 | --apps erts kernel stdlib crypto 36 | 37 | dialyzer: $(DEPSOLVER_PLT) 38 | @dialyzer --plt $(DEPSOLVER_PLT) -Wrace_conditions --src deps/*/src src test 39 | 40 | typer: $(DEPSOLVER_PLT) 41 | @typer -I include --plt $(DEPSOLVER_PLT) -r src 42 | 43 | distclean: clean 44 | @rm $(DEPSOLVER_PLT) 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | gen\_unix is an Erlang interface to Unix sockets. The main purpose of 2 | gen\_unix is to allow access to socket ancillary data required for fd 3 | and credential passing. 4 | 5 | WARNING: gen\_unix is still under development and going through a lot 6 | of change. 7 | 8 | Examples 9 | ======== 10 | 11 | * File descriptor passing 12 | 13 | Open 2 shell prompts and start an Erlang VM in each: 14 | 15 | # VM 1 16 | $ rebar shell 17 | 1> {ok, Ref} = gen_unix:start(). 18 | {ok,<0.35.0>} 19 | 20 | 2> {ok, Listen} = gen_unix:listen(Ref, "/tmp/test"). 21 | {ok,8} 22 | 23 | # VM 2 24 | $ sudo rebar shell 25 | 1> {ok, Ref} = gen_unix:start(). 26 | {ok,<0.35.0>} 27 | 28 | 2> {ok, Socket} = gen_unix:connect(Ref, "/tmp/test"). 29 | {ok,7} 30 | 31 | 2> {ok, FD1} = procket:socket(inet,raw,icmp). 32 | {ok,8} 33 | 34 | 3> {ok, FD2} = procket:socket(inet6,raw,icmp6). 35 | {ok,9} 36 | 37 | 4> {ok, Msg} = unixsock:msg({fdsend, [FD1,FD2]}). 38 | {ok,{msghdr,<<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,160,114,0, 39 | 232,20,127,0,0,1,...>>, 40 | [{msg_iov,<<>>},{msg_control,<<>>},{iov,[<<>>]}]}} 41 | 42 | 5> gen_unix:sendmsg(Ref, Socket, Msg). 43 | ok 44 | 45 | # Open another shell and send a few ping's 46 | 47 | # Back in VM 1 48 | 2> {ok, Socket} = gen_unix:accept(Ref, Listen). 49 | {ok,9} 50 | 51 | 3> {ok, Msg} = unixsock:msg({fdrecv, 2}). 52 | {ok,{msghdr,<<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,160,114,0, 53 | 128,198,127,0,0,1,...>>, 54 | [{msg_iov,<<>>},{msg_control,<<>>},{iov,[<<>>]}]}} 55 | 56 | 4> gen_unix:recvmsg(Ref, Socket, Msg). 57 | {ok,1} 58 | 59 | % [8,9] 60 | 5> {ok, [FD1, FD2]} = unixsock:msg(Msg). 61 | {ok,"\t\n"} 62 | 63 | 5> procket:read(FD1, 1024). 64 | {ok,<<69,0,0,84,236,114,0,0,56,1,38,238,173,194,43,69, 65 | 192,168,213,152,0,0,98,95,21,173,0,...>>} 66 | 67 | * Credential passing 68 | 69 | # VM 1 70 | $ rebar shell 71 | 1> {ok, Ref} = gen_unix:start(). 72 | {ok,<0.35.0>} 73 | 74 | 2> {ok, Listen} = gen_unix:listen(Ref, "/tmp/test"). 75 | {ok,8} 76 | 77 | # VM 2 78 | $ rebar shell 79 | 1> {ok, Ref} = gen_unix:start(). 80 | {ok,<0.35.0>} 81 | 82 | 2> {ok, Socket} = gen_unix:connect(Ref, "/tmp/test"). 83 | {ok,7} 84 | 85 | 2> {ok, Msg} = unixsock:msg(credsend). 86 | {ok,{msghdr,<<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,115,0,232, 87 | 20,127,0,0,1,...>>, 88 | [{msg_iov,<<>>},{iov,[<<>>]}]}} 89 | 90 | 3> gen_unix:sendmsg(Ref, Socket, Msg). 91 | ok 92 | 93 | # Back in VM 1 94 | 2> {ok, Socket} = gen_unix:accept(Ref, Listen). 95 | {ok,9} 96 | 97 | 3> {ok, Msg} = unixsock:msg(credrecv). 98 | {ok,{msghdr,<<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,115,0,128, 99 | 198,127,0,0,1,...>>, 100 | [{msg_iov,<<>>},{msg_control,<<>>},{iov,[<<>>]}]}} 101 | 102 | 4> unixsock:setsockopt(Socket, {credrecv, open}). 103 | ok 104 | 105 | 5> gen_unix:recvmsg(Ref, Socket, Msg). 106 | {ok,1} 107 | 108 | 6> unixsock:msg(Msg). 109 | {ok, [{pid,12866},{uid,1000},{gid,1000}]} 110 | 111 | 7> unixsock:setsockopt(Socket, {credrecv, close}). 112 | ok 113 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {deps, [ 2 | {procket, ".*", {git, "git://github.com/msantos/procket.git", {tag,"0.6.1"}}}, 3 | {inert, ".*", {git, "git://github.com/msantos/inert.git", {tag,"0.6.0"}}} 4 | ]}. 5 | 6 | {xref_checks, [undefined_function_calls]}. 7 | 8 | {eunit_opts, [verbose, {report, {eunit_surefire, [{dir, "."}]}}]}. 9 | -------------------------------------------------------------------------------- /src/gen_unix.app.src: -------------------------------------------------------------------------------- 1 | {application, gen_unix, 2 | [{description, "Interface to Unix sockets"}, 3 | {vsn, "0.1.2"}, 4 | {registered, []}, 5 | {applications, [kernel, stdlib]} 6 | ]}. 7 | -------------------------------------------------------------------------------- /src/gen_unix.erl: -------------------------------------------------------------------------------- 1 | %%% Copyright (c) 2013-2015, Michael Santos 2 | %%% 3 | %%% Permission to use, copy, modify, and/or distribute this software for any 4 | %%% purpose with or without fee is hereby granted, provided that the above 5 | %%% copyright notice and this permission notice appear in all copies. 6 | %%% 7 | %%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | %%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | %%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | %%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | %%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | %%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | %%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -module(gen_unix). 15 | -behaviour(gen_server). 16 | 17 | %% API 18 | -export([start/0, start/1, stop/1]). 19 | -export([start_link/1]). 20 | -export([ 21 | listen/2, 22 | connect/2, 23 | accept/2, accept/3, 24 | close/2, 25 | sendmsg/4, sendmsg/5, 26 | recvmsg/4, recvmsg/5, 27 | 28 | pollid/1 29 | ]). 30 | 31 | %% gen_server callbacks 32 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 33 | terminate/2, code_change/3]). 34 | 35 | -record(state, { 36 | poll, 37 | fds = dict:new() 38 | }). 39 | 40 | start() -> 41 | start_link([]). 42 | start(Options) when is_list(Options) -> 43 | start_link(Options). 44 | 45 | start_link(Options) -> 46 | gen_server:start_link(?MODULE, [Options], []). 47 | 48 | stop(Ref) -> 49 | catch gen_server:call(Ref, stop), 50 | ok. 51 | 52 | listen(Ref, Path) -> 53 | gen_server:call(Ref, {listen, Path}). 54 | 55 | accept(Ref, Socket) -> 56 | accept(Ref, Socket, []). 57 | accept(Ref, Socket, Options) -> 58 | Poll = pollid(Ref), 59 | Timeout = proplists:get_value(timeout, Options, infinity), 60 | case poll(Poll, Socket, [{mode,read}, {timeout, Timeout}]) of 61 | {ok,read} -> 62 | gen_server:call(Ref, {accept, Socket}); 63 | Error -> 64 | Error 65 | end. 66 | 67 | connect(Ref, Path) -> 68 | gen_server:call(Ref, {connect, Path}). 69 | 70 | close(Ref, Socket) -> 71 | gen_server:call(Ref, {close, Socket}). 72 | 73 | sendmsg(Ref, Socket, Buf, Msg) -> 74 | sendmsg(Ref, Socket, Buf, Msg, []). 75 | sendmsg(Ref, Socket, Buf, Msg, Options) -> 76 | Flags = proplists:get_value(flags, Options, 0), 77 | Poll = pollid(Ref), 78 | poll(Poll, Socket, [{mode,write}]), 79 | case unixsock:sendmsg(Socket, Buf, Flags, Msg) of 80 | ok -> 81 | ok; 82 | {ok, N} -> 83 | <<_:N/bytes, Rest/binary>> = Buf, 84 | sendmsg(Ref, Socket, Rest, Msg, Options); 85 | Error -> 86 | Error 87 | end. 88 | 89 | recvmsg(Ref, Socket, Bufsz, Msgsz) -> 90 | recvmsg(Ref, Socket, Bufsz, Msgsz, []). 91 | recvmsg(Ref, Socket, Bufsz, Msgsz, Options) -> 92 | Flags = proplists:get_value(flags, Options, 0), 93 | Timeout = proplists:get_value(timeout, Options, infinity), 94 | Poll = pollid(Ref), 95 | case poll(Poll, Socket, [{mode,read}, {timeout,Timeout}]) of 96 | {ok,read} -> 97 | unixsock:recvmsg(Socket, Bufsz, Flags, Msgsz); 98 | Error -> 99 | Error 100 | end. 101 | 102 | pollid(Ref) -> 103 | gen_server:call(Ref, pollid). 104 | 105 | 106 | %%-------------------------------------------------------------------- 107 | %%% gen_server callbacks 108 | %%-------------------------------------------------------------------- 109 | init([_Options]) -> 110 | process_flag(trap_exit, true), 111 | Poll = prim_inert:start(), 112 | {ok, #state{ 113 | poll = Poll 114 | }}. 115 | 116 | handle_call({listen, Path}, _From, #state{fds = FDs} = State) -> 117 | Reply = unixsock:listen(Path), 118 | FDs1 = case Reply of 119 | {ok, FD} -> 120 | dict:store(FD, Path, FDs); 121 | _Error -> 122 | FDs 123 | end, 124 | {reply, Reply, State#state{fds = FDs1}}; 125 | 126 | handle_call({accept, Socket}, _From, #state{fds = FDs} = State) -> 127 | Reply = unixsock:accept(Socket), 128 | FDs1 = case Reply of 129 | {ok, FD} -> 130 | dict:store(FD, undefined, FDs); 131 | _Error -> 132 | FDs 133 | end, 134 | {reply, Reply, State#state{fds = FDs1}}; 135 | 136 | handle_call({connect, Path}, _From, #state{fds = FDs} = State) -> 137 | Reply = unixsock:connect(Path), 138 | FDs1 = case Reply of 139 | {ok, FD} -> 140 | dict:store(FD, undefined, FDs); 141 | _Error -> 142 | FDs 143 | end, 144 | {reply, Reply, State#state{fds = FDs1}}; 145 | 146 | handle_call({close, FD}, _From, #state{fds = FDs} = State) -> 147 | Reply = unixsock:close(FD), 148 | FDs1 = case Reply of 149 | ok -> 150 | case dict:find(FD, FDs) of 151 | {ok, undefined} -> 152 | dict:erase(FD, FDs); 153 | {ok, Path} -> 154 | file:delete(Path), 155 | dict:erase(FD, FDs); 156 | _ -> 157 | FDs 158 | end; 159 | _Error -> 160 | FDs 161 | end, 162 | {reply, Reply, State#state{fds = FDs1}}; 163 | 164 | handle_call(pollid, _From, #state{poll = Poll} = State) -> 165 | {reply, Poll, State}; 166 | 167 | handle_call(stop, _From, State) -> 168 | {stop, normal, ok, State}. 169 | 170 | handle_cast(_Msg, State) -> 171 | {noreply, State}. 172 | 173 | % WTF 174 | handle_info(Info, State) -> 175 | error_logger:error_report([{wtf, Info}]), 176 | {noreply, State}. 177 | 178 | terminate(_Reason, #state{fds = FDs, poll = Poll}) -> 179 | dict:map(fun 180 | (FD, undefined) -> 181 | unixsock:close(FD); 182 | (FD, Path) -> 183 | unixsock:close(FD), 184 | file:delete(Path) 185 | end, FDs), 186 | prim_inert:stop(Poll), 187 | ok. 188 | 189 | code_change(_OldVsn, State, _Extra) -> 190 | {ok, State}. 191 | 192 | 193 | %%-------------------------------------------------------------------- 194 | %%% Internal functions 195 | %%-------------------------------------------------------------------- 196 | poll(Poll, Socket, Options) -> 197 | prim_inert:poll(Poll, Socket, Options). 198 | -------------------------------------------------------------------------------- /src/unixsock.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2013-2015, Michael Santos 2 | %% All rights reserved. 3 | %% 4 | %% Redistribution and use in source and binary forms, with or without 5 | %% modification, are permitted provided that the following conditions 6 | %% are met: 7 | %% 8 | %% Redistributions of source code must retain the above copyright 9 | %% notice, this list of conditions and the following disclaimer. 10 | %% 11 | %% Redistributions in binary form must reproduce the above copyright 12 | %% notice, this list of conditions and the following disclaimer in the 13 | %% documentation and/or other materials provided with the distribution. 14 | %% 15 | %% Neither the name of the author nor the names of its contributors 16 | %% may be used to endorse or promote products derived from this software 17 | %% without specific prior written permission. 18 | %% 19 | %% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | %% "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | %% LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 22 | %% FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 23 | %% COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 24 | %% INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 25 | %% BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | %% LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | %% CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | %% LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 29 | %% ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | %% POSSIBILITY OF SUCH DAMAGE. 31 | -module(unixsock). 32 | -include_lib("procket/include/procket.hrl"). 33 | %-include_lib("pkt/include/pkt.hrl"). 34 | 35 | -export([ 36 | listen/1, listen/2, 37 | connect/1, 38 | close/1, 39 | 40 | accept/1, 41 | 42 | setsockopt/2, 43 | 44 | msg/1, 45 | 46 | fd/1, 47 | cred/1, 48 | 49 | sendmsg/3, sendmsg/4, 50 | recvmsg/3, recvmsg/4, 51 | 52 | scm_rights/0, 53 | scm_creds/0, 54 | scm_timestamp/0, 55 | scm_bintime/0, 56 | so_passcred/0, 57 | so_peercred/0 58 | ]). 59 | 60 | -define(SCM_RIGHTS, ?MODULE:scm_rights()). 61 | -define(SCM_CREDENTIALS, ?SCM_CREDS). 62 | -define(SCM_CREDS, ?MODULE:scm_creds()). 63 | -define(SCM_TIMESTAMP, ?MODULE:scm_timestamp()). 64 | -define(SCM_BINTIME, ?MODULE:scm_bintime()). 65 | 66 | % XXX testing only, move these to procket 67 | -define(SO_PASSCRED, ?MODULE:so_passcred()). 68 | -define(SO_PEERCRED, ?MODULE:so_peercred()). 69 | 70 | -define(SIZEOF_CMSGHDR, 8 + 4 + 4). 71 | 72 | -spec listen(Path :: iodata()) -> {'ok',integer()} | {'error',file:posix()}. 73 | listen(Path) -> 74 | listen(Path, ?BACKLOG). 75 | 76 | -spec listen(Path :: iodata(), Backlog :: non_neg_integer()) -> 77 | {'ok',integer()} | {'error',file:posix()}. 78 | listen(Path, Backlog) when is_list(Path) -> 79 | listen(iolist_to_binary(Path), Backlog); 80 | listen(Path, Backlog) when is_binary(Path), byte_size(Path) < ?UNIX_PATH_MAX -> 81 | case procket:socket(?PF_LOCAL, ?SOCK_STREAM, 0) of 82 | {ok, Socket} -> 83 | Len = byte_size(Path), 84 | Sun = <<(procket:sockaddr_common(?PF_LOCAL, Len))/binary, 85 | Path/binary, 86 | 0:((procket:unix_path_max()-Len)*8)>>, 87 | listen_1(Socket, Backlog, procket:bind(Socket, Sun)); 88 | Error -> 89 | Error 90 | end. 91 | 92 | listen_1(Socket, Backlog, ok) -> 93 | case procket:listen(Socket, Backlog) of 94 | ok -> {ok, Socket}; 95 | Error -> Error 96 | end; 97 | listen_1(_Socket, _Backlog, Error) -> 98 | Error. 99 | 100 | connect(Path) when is_list(Path) -> 101 | connect(list_to_binary(Path)); 102 | connect(Path) when is_binary(Path), byte_size(Path) < ?UNIX_PATH_MAX -> 103 | {ok, Socket} = procket:socket(?PF_LOCAL, ?SOCK_STREAM, 0), 104 | Len = byte_size(Path), 105 | Sun = <<(procket:sockaddr_common(?PF_LOCAL, Len))/binary, 106 | Path/binary, 107 | 0:((procket:unix_path_max()-Len)*8)>>, 108 | case procket:connect(Socket, Sun) of 109 | ok -> 110 | {ok, Socket}; 111 | Error -> 112 | Error 113 | end. 114 | 115 | close(FD) -> 116 | procket:close(FD). 117 | 118 | accept(FD) -> 119 | procket:accept(FD). 120 | 121 | recvmsg(FD, Bufsz, Msgsz) -> 122 | recvmsg(FD, Bufsz, 0, Msgsz). 123 | 124 | recvmsg(FD, Bufsz, Flags, Msgsz) -> 125 | procket:recvmsg(FD, Bufsz, Flags, Msgsz). 126 | 127 | sendmsg(FD, Buf, Msg) -> 128 | procket:sendmsg(FD, Buf, 0, Msg). 129 | 130 | sendmsg(FD, Buf, Flags, Msg) -> 131 | procket:sendmsg(FD, Buf, Flags, Msg). 132 | 133 | cmsghdr(Level, Type, Data) when is_integer(Level), is_integer(Type), is_binary(Data) -> 134 | [{Level, Type, Data}]. 135 | 136 | msg({fdsend, FD}) when is_integer(FD); is_list(FD) -> 137 | Cmsg = cmsghdr(sol_socket(), ?SCM_RIGHTS, fd(FD)), 138 | {<<"x">>, Cmsg}; 139 | msg(credsend) -> 140 | Cmsg = case os:type() of 141 | {unix,linux} -> 142 | % Linux fills in the cmsghdr 143 | <<>>; 144 | {unix,_} -> 145 | % FreeBSD requires the cmsghdrcred to be allocated but 146 | % fills in the fields 147 | cmsghdr(sol_socket(), ?SCM_CREDENTIALS, cred([])) 148 | end, 149 | {<<"c">>, Cmsg}; 150 | 151 | msg(fdrecv) -> 152 | msg({fdrecv, 1}); 153 | msg({fdrecv, N}) when is_integer(N) -> 154 | Cmsgsz = 4 + 4 + (N * 4 * 8), 155 | {1, Cmsgsz}; 156 | msg(credrecv) -> 157 | Cmsgsz = 4 + 4 + (sizeof(ucred) * 8), 158 | {1, Cmsgsz}; 159 | 160 | msg([{Level, Type, Data} = Cmsg]) when is_integer(Level), is_integer(Type), is_binary(Data) -> 161 | msg_data(Cmsg). 162 | 163 | msg_data(Cmsg) -> 164 | SOL_SOCKET = sol_socket(), 165 | SCM_RIGHTS = ?SCM_RIGHTS, 166 | SCM_CREDENTIALS = ?SCM_CREDENTIALS, 167 | case Cmsg of 168 | {SOL_SOCKET, SCM_RIGHTS, Data} -> 169 | {ok, fd(Data)}; 170 | {SOL_SOCKET, SCM_CREDENTIALS, Data} -> 171 | {ok, cred(Data)}; 172 | {SOL_SOCKET, Type, Data} -> 173 | error_logger:info_report([{type, Type}, {data, Data}]), 174 | {error, esocktnosupport}; 175 | _ -> 176 | {error, einval} 177 | end. 178 | 179 | fd(FDs) when is_binary(FDs) -> 180 | [ FD || <> <= FDs ]; 181 | fd(FDs) when is_list(FDs) -> 182 | << <> || FD <- FDs >>. 183 | 184 | cred(Data) -> 185 | cred(os:type(), Data). 186 | 187 | % struct ucred 188 | % { 189 | % pid_t pid; /* PID of sending process. */ 190 | % uid_t uid; /* UID of sending process. */ 191 | % gid_t gid; /* GID of sending process. */ 192 | % }; 193 | cred({unix, linux}, << 194 | Pid:4/native-signed-integer-unit:8, 195 | Uid:4/native-unsigned-integer-unit:8, 196 | Gid:4/native-unsigned-integer-unit:8 197 | >>) -> 198 | [{pid, Pid}, {uid, Uid}, {gid, Gid}]; 199 | cred({unix, linux}, Fields) when is_list(Fields) -> 200 | Pid = proplists:get_value(pid, Fields, list_to_integer(os:getpid())), 201 | Uid = proplists:get_value(uid, Fields, 0), % XXX no way to get uid? 202 | Gid = proplists:get_value(gid, Fields, 0), % XXX or gid? 203 | <>; 206 | 207 | % #define CMGROUP_MAX 16 208 | % struct cmsgcred { 209 | % pid_t cmcred_pid; /* PID of sending process */ 210 | % uid_t cmcred_uid; /* real UID of sending process */ 211 | % uid_t cmcred_euid; /* effective UID of sending process */ 212 | % gid_t cmcred_gid; /* real GID of sending process */ 213 | % short cmcred_ngroups; /* number or groups */ 214 | % gid_t cmcred_groups[CMGROUP_MAX]; /* groups */ 215 | % }; 216 | cred({unix, freebsd}, << 217 | Pid:4/native-unsigned-integer-unit:8, 218 | Uid:4/native-unsigned-integer-unit:8, 219 | Euid:4/native-unsigned-integer-unit:8, 220 | Gid:4/native-unsigned-integer-unit:8, 221 | Ngroups:2/native-signed-integer-unit:8, 222 | Rest/binary 223 | >>) -> 224 | Pad = procket:wordalign(4 + 4 + 4 + 4 + 2) * 8, 225 | Num = Ngroups * 4, % gid_t is 4 bytes 226 | <<_:Pad, Gr:Num/bytes, _/binary>> = Rest, 227 | Groups = [ N || <> <= Gr ], 228 | [{pid, Pid}, {uid, Uid}, {euid, Euid}, {gid, Gid}, {groups, Groups}]; 229 | cred({unix, freebsd}, Fields) when is_list(Fields) -> 230 | Size = erlang:system_info({wordsize, external}), 231 | Pid = proplists:get_value(pid, Fields, list_to_integer(os:getpid())), 232 | Uid = proplists:get_value(uid, Fields, 0), 233 | Euid = proplists:get_value(euid, Fields, 0), 234 | Gid = proplists:get_value(gid, Fields, 0), 235 | Groups = proplists:get_value(groups, Fields, [0]), 236 | Pad0 = procket:wordalign(4 + 4 + 4 + 4 + 2) * 8, 237 | Ngroups = length(Groups), 238 | Gr = << <> || N <- Groups >>, 239 | Pad1 = (16 - Ngroups) * 4 * 8, 240 | <>; 248 | 249 | % OpenBSD 250 | % struct ucred { 251 | % u_int cr_ref; /* reference count */ 252 | % uid_t cr_uid; /* effective user id */ 253 | % gid_t cr_gid; /* effective group id */ 254 | % short cr_ngroups; /* number of groups */ 255 | % gid_t cr_groups[NGROUPS]; /* groups */ 256 | % }; 257 | % 258 | % NetBSD 259 | % struct uucred { 260 | % unsigned short cr_unused; /* not used, compat */ 261 | % uid_t cr_uid; /* effective user id */ 262 | % gid_t cr_gid; /* effective group id */ 263 | % short cr_ngroups; /* number of groups */ 264 | % gid_t cr_groups[NGROUPS_MAX]; /* groups */ 265 | % }; 266 | cred({unix, _}, << 267 | Ref:4/native-unsigned-integer-unit:8, 268 | Uid:4/native-unsigned-integer-unit:8, 269 | Gid:4/native-unsigned-integer-unit:8, 270 | Ngroups:2/native-signed-integer-unit:8, 271 | Rest/binary 272 | >>) -> 273 | Num = Ngroups * 4, 274 | <> = Rest, 275 | Groups = [ N || <> <= Gr ], 276 | [{ref, Ref}, {uid, Uid}, {gid, Gid}, {groups, Groups}]; 277 | cred({unix, _}, Fields) when is_list(Fields) -> 278 | Ref = proplists:get_value(ref, Fields, 0), 279 | Uid = proplists:get_value(uid, Fields, 0), 280 | Gid = proplists:get_value(gid, Fields, 0), 281 | Groups = proplists:get_value(groups, Fields, [0]), 282 | Ngroups = length(Groups), 283 | Gr = << <> || N <- Groups >>, 284 | Pad = (16 - Ngroups) * 8, % NGROUPS_MAX = 16 285 | <>. 290 | 291 | sizeof(ucred) -> 292 | sizeof(os:type(), ucred). 293 | sizeof({unix,linux}, ucred) -> 294 | 4 + 4 + 4; 295 | sizeof({unix,_}, ucred) -> 296 | Len = 4 + 4 + 4 + 4 + 2, 297 | Pad = procket:wordalign(Len), 298 | Len + Pad + 4 * 16. 299 | 300 | 301 | scm_rights() -> 302 | 16#01. 303 | 304 | % OpenBSD uses getsockopt(SOL_SOCKET, SO_PEERCRED 305 | % XXX Return undefined or {error, unsupported}? 306 | scm_creds() -> 307 | proplists:get_value(os:type(), [ 308 | {{unix,linux}, 16#02}, 309 | {{unix,freebsd}, 16#03}, 310 | {{unix,netbsd}, 16#04} 311 | ]). 312 | 313 | scm_timestamp() -> 314 | proplists:get_value(os:type(), [ 315 | {{unix,freebsd}, 16#02}, 316 | {{unix,netbsd}, 16#08} 317 | ]). 318 | 319 | scm_bintime() -> 320 | proplists:get_value(os:type(), [ 321 | {{unix,freebsd}, 16#04} 322 | ]). 323 | 324 | so_passcred() -> 325 | proplists:get_value(os:type(), [ 326 | {{unix,linux}, 16} 327 | ]). 328 | 329 | so_peercred() -> 330 | proplists:get_value(os:type(), [ 331 | {{unix,openbsd}, 16#1022} 332 | ]). 333 | 334 | sol_socket() -> 335 | proplists:get_value(os:type(), [ 336 | {{unix,linux}, 1} 337 | ], 16#ffff). 338 | 339 | setsockopt(Socket, {credrecv, Status}) -> 340 | setsockopt(os:type(), Socket, credrecv, Status). 341 | setsockopt({unix,linux}, Socket, credrecv, open) -> 342 | procket:setsockopt(Socket, sol_socket(), ?SO_PASSCRED, 343 | <<1:4/native-unsigned-integer-unit:8>>); 344 | setsockopt({unix,linux}, Socket, credrecv, close) -> 345 | procket:setsockopt(Socket, sol_socket(), ?SO_PASSCRED, 346 | <<0:4/native-unsigned-integer-unit:8>>); 347 | setsockopt({unix,_}, _Socket, credrecv, _) -> 348 | ok. 349 | -------------------------------------------------------------------------------- /test/gen_unix_tests.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2013-2015, Michael Santos 2 | %% All rights reserved. 3 | %% 4 | %% Redistribution and use in source and binary forms, with or without 5 | %% modification, are permitted provided that the following conditions 6 | %% are met: 7 | %% 8 | %% Redistributions of source code must retain the above copyright 9 | %% notice, this list of conditions and the following disclaimer. 10 | %% 11 | %% Redistributions in binary form must reproduce the above copyright 12 | %% notice, this list of conditions and the following disclaimer in the 13 | %% documentation and/or other materials provided with the distribution. 14 | %% 15 | %% Neither the name of the author nor the names of its contributors 16 | %% may be used to endorse or promote products derived from this software 17 | %% without specific prior written permission. 18 | %% 19 | %% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | %% "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | %% LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 22 | %% FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 23 | %% COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 24 | %% INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 25 | %% BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | %% LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | %% CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | %% LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 29 | %% ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | %% POSSIBILITY OF SUCH DAMAGE. 31 | -module(gen_unix_tests). 32 | 33 | -compile(export_all). 34 | 35 | -include_lib("eunit/include/eunit.hrl"). 36 | 37 | -define(SOCKDIR, "/tmp/gen_unix"). 38 | 39 | fd_array_test() -> 40 | FD = [9,10,11,12,13,14], 41 | FD = unixsock:fd(unixsock:fd(FD)). 42 | 43 | ucred_struct_test() -> 44 | % Layout for struct ucred on this platform 45 | Cred = unixsock:cred([]), 46 | Cred = unixsock:cred(unixsock:cred(Cred)). 47 | 48 | socket_test_() -> 49 | {setup, 50 | fun start/0, 51 | fun stop/1, 52 | fun run/1 53 | }. 54 | 55 | start() -> 56 | file:make_dir(?SOCKDIR), 57 | {ok, Ref} = gen_unix:start(), 58 | Ref. 59 | 60 | stop(Ref) -> 61 | gen_unix:stop(Ref). 62 | 63 | run(Ref) -> 64 | [ 65 | credpass(Ref), 66 | fdpass(Ref) 67 | ]. 68 | 69 | credpass(Ref) -> 70 | {ok, Socket0} = gen_unix:listen(Ref, ?SOCKDIR ++ "/cred"), 71 | spawn(fun() -> 72 | os:cmd("erl -detached -pa ../ebin ../deps/*/ebin ebin deps/*/ebin -s gen_unix_tests credsend -s init stop") 73 | end), 74 | {ok, Socket} = gen_unix:accept(Ref, Socket0), 75 | 76 | {Bufsz, Msgsz} = unixsock:msg(credrecv), 77 | 78 | ok = unixsock:setsockopt(Socket, {credrecv, open}), 79 | {ok, _Buf, _Flags, Ctl} = gen_unix:recvmsg(Ref, Socket, Bufsz, Msgsz), 80 | ok = unixsock:setsockopt(Socket, {credrecv, close}), 81 | 82 | % Check for common fields 83 | {ok, Ucred} = unixsock:msg(Ctl), 84 | Uid = proplists:get_value(uid, Ucred), 85 | Gid = proplists:get_value(gid, Ucred), 86 | Pid = proplists:get_value(pid, Ucred), 87 | % error_logger:info_report(Ucred), 88 | 89 | [ 90 | ?_assertEqual(true, is_integer(Uid)), 91 | ?_assertEqual(true, is_integer(Gid)), 92 | ?_assertEqual(true, is_integer(Pid)), 93 | ?_assertNotEqual( 94 | {0, 4294967295, 4294967295}, 95 | {Pid, Uid, Gid} 96 | ) 97 | ]. 98 | 99 | fdpass(Ref) -> 100 | {ok, Socket0} = gen_unix:listen(Ref, ?SOCKDIR ++ "/fd"), 101 | spawn(fun() -> 102 | os:cmd("erl -detached -pa ../ebin ../deps/*/ebin ebin deps/*/ebin -s gen_unix_tests fdsend -s init stop") 103 | end), 104 | {ok, Socket} = gen_unix:accept(Ref, Socket0), 105 | 106 | {Bufsz, Msgsz} = unixsock:msg({fdrecv, 2}), 107 | 108 | {ok, _Buf, _Flags, Ctl} = gen_unix:recvmsg(Ref, Socket, Bufsz, Msgsz), 109 | 110 | {ok, [FD1,FD2]} = unixsock:msg(Ctl), 111 | true = is_integer(FD1) and (FD1 > 2), 112 | true = is_integer(FD2) and (FD2 > 2), 113 | 114 | %error_logger:info_report([{fd1, FD1}, {fd2, FD2}]), 115 | 116 | {ok, Socket1} = gen_udp:open(0, [binary, {fd, FD1}, {active, false}]), 117 | {ok, Socket2} = gen_udp:open(0, [binary, {fd, FD2}, {active, false}]), 118 | 119 | [ 120 | ?_assertMatch({ok, {{127,0,0,1}, _, <<0,1,2,3,4,5,6,7,8,9>>}}, gen_udp:recv(Socket1, 20)), 121 | ?_assertMatch({ok, {{127,0,0,1}, _, <<0,1,2,3,4,5,6,7,8,9>>}}, gen_udp:recv(Socket2, 20)) 122 | ]. 123 | 124 | credsend() -> 125 | {ok, Ref} = gen_unix:start(), 126 | {ok, Socket} = gen_unix:connect(Ref, ?SOCKDIR ++ "/cred"), 127 | {Buf, Msg} = unixsock:msg(credsend), 128 | ok = gen_unix:sendmsg(Ref, Socket, Buf, Msg), 129 | timer:sleep(1000). 130 | 131 | fdsend() -> 132 | {ok, Ref} = gen_unix:start(), 133 | {ok, Socket} = gen_unix:connect(Ref, ?SOCKDIR ++ "/fd"), 134 | 135 | % Open 2 random UDP ports 136 | {ok, Socket1} = gen_udp:open(0, [{active,false}]), 137 | {ok, Socket2} = gen_udp:open(0, [{active,false}]), 138 | 139 | {ok, Port1} = inet:port(Socket1), 140 | {ok, Port2} = inet:port(Socket2), 141 | 142 | {ok, FD1} = inet:getfd(Socket1), 143 | {ok, FD2} = inet:getfd(Socket2), 144 | 145 | {Buf, Msg} = unixsock:msg({fdsend, [FD1, FD2]}), 146 | ok = gen_unix:sendmsg(Ref, Socket, Buf, Msg), 147 | 148 | ok = gen_udp:close(Socket1), 149 | ok = gen_udp:close(Socket2), 150 | 151 | % Open 2 new sockets 152 | {ok, Socket3} = gen_udp:open(0, [{active,false}]), 153 | {ok, Socket4} = gen_udp:open(0, [{active,false}]), 154 | 155 | ok = gen_udp:send(Socket3, {127,0,0,1}, Port1, <<0,1,2,3,4,5,6,7,8,9>>), 156 | ok = gen_udp:send(Socket4, {127,0,0,1}, Port2, <<0,1,2,3,4,5,6,7,8,9>>), 157 | timer:sleep(1000). 158 | --------------------------------------------------------------------------------