%%% 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, Config), start_up(Config, Config). start(MgrPort, Config) -> registry:start(), manager:start(MgrPort, Config), 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)), %% If we only have a 4-part, or 2-part, msg result, it's one of %% the new types, and so there's no fiddling to be done with it. MsgToSend = case MsgResult of closed -> path_mgr:msg(Manager, Direction, MsgResult); {_,_,_,_,_} -> path_mgr:msg(Manager, Direction, MsgResult); {_,_,_,_} -> MsgResult; {_,_,_} -> MsgResult end, case MsgToSend 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 = <>, send(Sock, Header), send_piece(Sock, Control), send_piece(Sock, Rec); send_msg(Sock, {MsgType, Length, Other, Data}) -> Header = <>, send(Sock, Header), send_piece(Sock, Data); send_msg(Sock, {MsgType, Length, Other}) -> Header = <>, send(Sock, Header); 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} -> <> = B, case MsgType of ?ACK -> tradition_msg(Sock, MsgType, Word1, Word2); ?HANDSHAKE -> tradition_msg(Sock, MsgType, Word1, Word2); ?REP_MESSAGE -> tradition_msg(Sock, MsgType, Word1, Word2); ?HEARTBEAT -> tradition_msg(Sock, MsgType, Word1, Word2); ?APP_MSG -> simple_msg(Sock, MsgType, Word1, Word2); ?APP_RESPONSE -> simple_msg(Sock, MsgType, Word1, Word2); ?RESP_ERROR -> {Sock, MsgType, Word1, Word2}; ?OWN_MSG -> simple_msg(Sock, MsgType, Word1, Word2) end; {error, closed} -> throw(closed); {error,enotconn} -> throw(closed) end. tradition_msg(Sock, MsgType, ControlLength, RecLength) -> Control = get_piece(Sock, ControlLength), Rec = get_piece(Sock, RecLength), {MsgType, ControlLength, RecLength, Control, Rec}. simple_msg(Sock, MsgType, Length, Word2) -> Msg = get_piece(Sock, Length), {MsgType, Length, Word2, Msg}. get_piece(Sock, Length) -> if Length == 0 -> nil; Length > 0 -> case recv(Sock, Length) of {ok, Piece} -> Piece; {error, closed} -> throw(closed); {error,enotconn} -> throw(closed) end end.