File: fiddler.erl

package info (click to toggle)
db 5.1.29-9%2Bdeb8u1
  • links: PTS, VCS
  • area: main
  • in suites: jessie
  • size: 150,396 kB
  • sloc: ansic: 400,169; java: 94,399; tcl: 70,967; sh: 37,399; cs: 30,758; cpp: 21,132; perl: 14,227; xml: 9,854; makefile: 3,777; yacc: 1,003; awk: 942; sql: 801; erlang: 461; python: 216; php: 24; asm: 14
file content (225 lines) | stat: -rw-r--r-- 8,804 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
%%% TODO: need to figure out this 'inet6' setting in the tcp options:
%%% It (and *not* 'inet') seems to be necessary when I run on Mac.
%%% Things work with neither setting on Linux and Windows -- not sure
%%% if things work there with the setting though.

%%% When the manager receives a command to install a munger, it can
%%% only affect an existing connection, because the way it works is to
%%% send the info to the registered path_mgr processes.  It doesn't
%%% save up a record of active mungers to bequeath to any connections
%%% that become established after that.
%%%
%%% I think that's a shortcoming that should be fixed.
%%%
%%% We might also need a way to remove a munger, although not
%%% currently.

%%% Hmm, is there any reason why the various path pairs need be
%%% related to each other?  Would it make sense to start [{7001,6001},
%%% {7000,6000}] first, and then later add {7002,6002} to the chaos?
%%% I think that might be fine, but only if the site at 6002 truly
%%% isn't running yet, because if it is, it could send a message that
%%% would need munging.  Similarly, it's important that no running
%%% site have that in its configuration yet, even if the new site
%%% isn't running yet.
%%%
%%% It's not needed yet, but I could imagine a need to target a
%%% command to an as yet unestablished connection/path.  Perhaps it
%%% could be directed to 'new'(?), or {6001,6000,new} (?).  Somewhere
%%% we'd need to hold onto that list, and give it (or some part of it)
%%% to new connections as they're getting established.  The point is,
%%% if you wait until after the connection is established and the
%%% sites have talked to each other, it might be too late to do what
%%% you need to do.


%%% Need to be able to:
%%%   2. remove previously installed munges (shall we give each one a
%%%      serial number or something?)
%%%   3. sometimes it's a little more complicated, and we need a way
%%%      to send a message or signal to an existing munge and/or even
%%%      the recv/send-er (in the case of socket congestion blockage,
%%%      when we want to free it up again).

%%% An interesting feature, that could be useful, maybe even
%%% essential: we could keep a record of all connections, even after
%%% they've been closed, for the purpose of analysis by the test.
%%% Obviously they would be keyed by site-pair (as expressed by port
%%% numbers, similarly to how we addressing path-specific munger
%%% commands).  But they could also include establishment timestamp,
%%% so that a test could distinguish between possibly multiple paths
%%% between the same endpoints.
%%%
%%% With that, a command could request that a path count various
%%% statistics about the messages that pass through (e.g., did we see
%%% any heartbeats on this connection?  We shouldn't, if we're in
%%% mixed-version mode.  Although I suppose an old site would simply
%%% crash if we made that mistake.)  A later command could then come
%%% back and request these statistics.

%% Also, it may be that the forward port isn't listening yet, in which
%% case we could get econnrefused.  Again, it's not terribly
%% disastrous to just let the error report close the connection.  But
%% we should handle it.  Besides, better than closing the initiating
%% connection might be to not even start listening until we see that
%% our forward port is listening.  That's more difficult, and could
%% conceivably bother the target site, but it's more realistic in
%% terms of what the connecting site should see.  Hmm, hard to know
%% which way is better, and whether it's even worth worrying about.
%%
%% Well, here's another thing to think about: it probably doesn't work
%% merely to avoid listening in the beginning, because the site could
%% die in the middle.  We certainly want ultimately to be able to test
%% those kinds of situations.  Hmm, at least for now, perhaps it's all
%% right just to close the incoming connection when we can't make the
%% outgoing connection, because repmgr doesn't really make much
%% difference between an EOF and an error.  But someday it might make
%% a difference.
%%
%% Test functions:
%% - stop reading (to block progress)
%% - discard everything (heartbeats)
%% - discard acks
%% - delay acks
%%
%% Each pair of sites (each link we could care about controlling) has
%% two possible ways it might get set up (in the most general case,
%% though often it's easy enough to control it more strictly).
%%
%% A munge function can be specified as applying only to a specified
%% path, or to all paths.  For example,
%%
%%     {{6000,6001},page_clog}
%%
%% says to apply the page_clog function to the path going from the
%% site listening on 6000 to the site listening on 6001 (both expressed
%% as real port numbers, not the spoofed port numbers).
%%
%%
%% There's another, rather different way of looking at how this gets
%% configured: instead of each site having one fiddler "sheilding" its
%% incoming connections, you could have fiddlers at just one site,
%% completely "wrapping" it, so that it takes not only all incoming
%% connections, but outgoing connections as well.  Consequences: (1)
%% it really focuses on that one site, making it the site under test
%% -- all the others are just going along for the ride.  You could
%% even imagine making them fake.  (2) Other sites talk directly to
%% each other, so we have no control over traffic between them.
%%
%% Site A:
%%     local:  6000
%% Site B:
%%     local:  6001
%%     remote: 7000
%% Site C:
%%     local:  6002
%%     remote: 6000
%% [{7001,6001},{7000,6000},{7002,6002}
%%
%% However, this might be confusing, and is probably counter to some
%% of the higher-level assumptions I've made in setting up
%% transformations.


-module(fiddler).

-export([start/1, start/2, main/2, do_accept/3, slave/4]).
-import(lists,[keysearch/3,foreach/2]).
-import(gen_tcp,[listen/2,accept/1,recv/2,connect/3,send/2]).

-define(MANAGER_PORT, 8000).

-include("rep_literals.hrl").

%% Config is a list of {spoofed,real} port numbers.  (For now
%% everything's on localhost.)
%%
%% TODO: shouldn't we use records for those tuples?

start(Config) ->
    registry:start(),
    manager:start(?MANAGER_PORT),
    start_up(Config, Config).

start(MgrPort, Config) ->
    registry:start(),
    manager:start(MgrPort),
    start_up(Config, Config).

%% For each pair specified in the config, spawn off a listener to
%% spoof the given pair, passing it its own pair, plus the total list
%% of all pairs.
%% 
start_up([H|T], Config) ->
    spawn(fiddler, main, [H, Config]),
    start_up(T, Config);
start_up([], _Config) ->
    ok.

main(Me, Config) ->
    {Spoofed, _Real} = Me,
    {ok, LSock} = listen(Spoofed, 
                         [binary, inet, inet6, {packet,raw}, {active, false}, {reuseaddr, true}]),
    do_accept(Me, Config, LSock).

do_accept(Me, Config, LSock) ->
    {_Spoofed, Real} = Me,
    {ok, Sock} = accept(LSock),
    {ok, TargetSock} = connect("localhost", Real, 
                               [binary, inet, inet6, {packet, raw}, {active, false}]),
    Mgr = path_mgr:start(Me, Config, Sock, TargetSock),
    spawn(fiddler, slave, [Sock, TargetSock, Mgr, receiving]),
    spawn(fiddler, slave, [TargetSock, Sock, Mgr, sending]),
    do_accept(Me, Config, LSock).


slave(Sock, Fwd, Manager, Direction) ->
    MsgResult = (catch get_one(Sock)),
    case path_mgr:msg(Manager, Direction, MsgResult) of
        quit ->
            ok;
        Msg ->                                  % or should I construct {ok,Msg}??
            send_msg(Fwd, Msg),
            slave(Sock, Fwd, Manager, Direction)
    end.

send_msg(Sock, {MsgType, ControlLength, RecLength, Control, Rec}) ->
    Header = <<MsgType:8, ControlLength:32/big, RecLength:32/big>>,
    send(Sock, Header),
    send_piece(Sock, Control),
    send_piece(Sock, Rec);
send_msg(_Sock, nil) ->
    ok.

send_piece(_Sock, nil) ->
    ok;
send_piece(Sock, Piece) ->
    send(Sock, Piece).

get_one(Sock) ->
    case recv(Sock, 9) of
        {ok, B} ->
            <<MsgType, ControlLength:32, RecLength:32>> = B,
            Control = get_piece(Sock, ControlLength),
            Rec = get_piece(Sock, RecLength),
            {MsgType, ControlLength, RecLength, Control, Rec};
        {error, closed} ->
            throw(closed);
        {error,enotconn} ->                     % ??? temp hack experiment
            throw(closed)
    end.

get_piece(Sock, Length) ->    
    if
        Length == 0 ->
            nil;
        Length > 0 ->
            case recv(Sock, Length) of
                {ok, Piece} ->
                    Piece;
                {error, closed} ->
                    throw(closed);
                {error,enotconn} ->             % ??? temp hack experiment
                    throw(closed)
            end
    end.