IptablesRuleExecutor

package com.spawnlabs.server.dmfs.iptables;

import com.spawnlabs.server.dmfs.DmfsOperationException;

import org.apache.commons.lang.StringUtils;

import static com.spawnlabs.server.dmfs.DmfsNames.SPC;

import static com.spawnlabs.server.dmfs.DmfsNames.ls;

import static com.spawnlabs.server.dmfs.DmfsProcessException.*;

import static com.spawnlabs.server.dmfs.iptables.RuleExecutor.Command.iptables_restore_start;

import static com.spawnlabs.server.dmfs.iptables.RuleExecutor.Command.iptables_restore_stop;

import static com.spawnlabs.server.dmfs.iptables.RuleExecutor.Command.query_rule;

/**

*/

public class IptablesRuleExecutor

extends AbstractRuleExecutor {

// Exit code 2: Errors which appear to be caused by invalid or abused command line parameters

// Exit code 1: other errors

@Override

public DmfsExecuteResult applyStartRules(String rules) throws RuleExecutionException, IptablesStateViolationException {

try {

return runSynchronously(iptables_restore_start, rules, null, null, null);

} catch (DmfsExecuteException dee) {

if (dee.getExitValue() == 1) {

// Lemme guess: error message is "Process exited with an error: 1 (Exit value: 1)"

// And if you were starting a play session, it said: "line 2 failed". And that's all.

// Let's assume that, since we got this far, all arguments in this call were validated.

// And let's assume that, since we got an ExecuteException, our code didn't break.

// In that case, we have some kind of unspecified IptablesStateViolationException.

throw new IptablesStateViolationException(DmfsOperationException.IPTABLES_ADD_CONNECTION_ALREADY_EXISTS,

dee,

"Trying to add rules that already exist?",

rules);

}

throw new RuleExecutionException(IPTABLES_ERROR, dee, rules);

}

}

@Override

public DmfsExecuteResult applyStopRules(String rules) throws RuleExecutionException, IptablesStateViolationException {

try {

return runSynchronously(iptables_restore_stop, rules, null, null, null);

} catch (DmfsExecuteException dee) {

String errOut;

if (dee.getExitValue() == 2 && !StringUtils.isEmpty(errOut = dee.getErrorOut())) {

if (errOut.contains("Couldn't load target") && errOut.contains(":/lib64/iptables/libipt_")) {

// Trying to delete rules that no longer exist looks like this:

// -D PREROUTING -j XYZ

// After the -j is a "target". Each target corresonds to a kernel module that gets auto-loaded when you reference it.

// In this case, it's supposed to be a Chain which, if it existed, would be resolved as the target. If it doesn't exist

// iptables tries to load the module for target "XYZ". And, it can't.

// The error msg will look like:

// Couldn't load target `XYZ':/lib64/iptables/libipt_XYZ.so

// (Where 'XYZ' is the play session token.)

// This is a case of trying to delete a rule that doesn't exist

throw new IptablesStateViolationException(DmfsOperationException.IPTABLES_CONNECTION_DOESNT_EXIST,

dee,

"Trying to delete rules that no longer exist?",

rules);

}

}

throw new RuleExecutionException(IPTABLES_ERROR, dee, rules);

}

}

@Override

public DmfsExecuteResult queryPlaySession(String rules) throws RuleExecutionException, IptablesStateViolationException {

try {

return runSynchronously(query_rule, rules, null, null, null);

} catch (DmfsExecuteException dee) {

String errOut;

if (dee.getExitValue() == 1 && !StringUtils.isEmpty(errOut = dee.getErrorOut())) {

if (errOut.contains("iptables-restore: line 2 failed")) {

// In this case, we're listing rules based on token. The input's 1st two lines look like:

// *nat

// --list $token-- numeric-- verbose-- line - numbers

// If no chain by the name "$token" exists, that line fails.

// What this means for us is that they are querying a connection that doesn't exist.

// At this point in the stack, we don't know the token. Throw the exception up, and let the next level add that info.

throw new IptablesStateViolationException(DmfsOperationException.IPTABLES_CONNECTION_DOESNT_EXIST,

dee,

"Rule-listing failed.",

rules);

}

}

throw new RuleExecutionException(IPTABLES_ERROR, dee, rules);

}

}

@Override

public DmfsExecuteResult queryAllPlaySessions() throws RuleExecutionException {

DmfsExecuteResult result = super.queryAllPlaySessions();

String commandOutput;

if (!StringUtils.isBlank(commandOutput = result.getCommandOutput())) {

String[] lines = commandOutput.split(ls);

for (int i = 0, linesLength = lines.length; i < linesLength; i++) {

String line = lines[i];

if (!line.startsWith(CHAIN)) {

continue;

}

if (isABuiltInChain(line)) {

continue;

}

// If we get here, it's one of our connection chains

ConnectionInfo ci = new ConnectionInfo(getChainName(line), lines[i + 2]).invoke();

result.addConnectionInfo(ci);

}

}

return result;

}

protected static String getChainName(String line) {

//Chain ABC (1 references)

return line.split(SPC)[1];

}

protected static boolean isABuiltInChain(String line) {

return line.startsWith(CHAIN_S + CHAIN_FORWARD) || line.startsWith(CHAIN_S + CHAIN_OUTPUT) || line.startsWith(CHAIN_S + CHAIN_INPUT);

}

}