1、各Openfire服务器得设置不同的域名,即 ofProperty表xmpp.domain属性,如设置 192.168.1.46等。
2、Dialback提供一种弱身份验证的方式,要使用这种方式可以将 ofproperty表中“xmpp.server.tls.enabled” 设置为false,并将“xmpp.server.dialback.enabled”设置为true。
3、建立到对方的路由:
LocalOutgoingServerSession类 authenticateDomain方法:
View Code
1
public
static boolean authenticateDomain(String domain, String hostname) {
2 OutgoingServerSession session;
3 ......
4 session = createOutgoingSession(domain, hostname, port);
5
if (session !=
null) {
6
//
Add the validated domain as an authenticated domain
7
session.addAuthenticatedDomain(domain);
8
//
Add the new hostname to the list of names that the server may have
9
session.addHostname(hostname);
10
//
Notify the SessionManager that a new session has been created
11
sessionManager.outgoingServerSessionCreated((LocalOutgoingServerSession) session);
12
return
true;
13 }
14 ......
15 }
3.1 Socket探测对方5269端口是否开放。
LocalOutgoingServerSession类 createOutgoingSession方法:
View Code
1
private
static LocalOutgoingServerSession createOutgoingSession(String domain, String hostname,
2
int port) {
3
boolean useTLS = JiveGlobals.getBooleanProperty("xmpp.server.tls.enabled",
true);
4 RemoteServerConfiguration configuration = RemoteServerManager.getConfiguration(hostname);
5
if (configuration !=
null) {
6
//
TODO Use the specific TLS configuration for this remote server
7
//
useTLS = configuration.isTLSEnabled();
8
}
9
10
//
Connect to remote server using XMPP 1.0 (TLS + SASL EXTERNAL or TLS + server dialback or server dialback)
11
SocketConnection connection =
null;
12 String realHostname =
null;
13
int realPort = port;
14 Socket socket =
new Socket();
15
try {
16
//
Get the real hostname to connect to using DNS lookup of the specified hostname
17
DNSUtil.HostAddress address = DNSUtil.resolveXMPPServerDomain(hostname, port);
18 realHostname = address.getHost();
19 realPort = address.getPort();
20 Log.debug("LocalOutgoingServerSession: OS - Trying to connect to " + hostname + ":" + port +
21 "(DNS lookup: " + realHostname + ":" + realPort + ")");
22
//
Establish a TCP connection to the Receiving Server
23
socket.connect(
new InetSocketAddress(realHostname, realPort),
24 RemoteServerManager.getSocketTimeout());
25 Log.debug("LocalOutgoingServerSession: OS - Plain connection to " + hostname + ":" + port + " successful");
26 }
27
catch (Exception e) {
28 Log.error("Error trying to connect to remote server: " + hostname +
29 "(DNS lookup: " + realHostname + ":" + realPort + ")", e);
30
return
null;
31 }
3.2 尝试server dialback(XMPP 1.0),建立信任通道。
3.2.1 LocalOutgoingServerSession类 createOutgoingSession方法:
View Code
1
//
Check if we are going to try server dialback (XMPP 1.0)
2
else
if (ServerDialback.isEnabled() && features.element("dialback") !=
null) {
3 Log.debug("LocalOutgoingServerSession: OS - About to try connecting using server dialback XMPP 1.0 with: " + hostname);
4 ServerDialback method =
new ServerDialback(connection, domain);
5 OutgoingServerSocketReader newSocketReader =
new OutgoingServerSocketReader(reader);
6
if (method.authenticateDomain(newSocketReader, domain, hostname, id)) {
7 Log.debug("LocalOutgoingServerSession: OS - SERVER DIALBACK XMPP 1.0 with " + hostname + " was successful");
8 StreamID streamID =
new BasicStreamIDFactory().createStreamID(id);
9 LocalOutgoingServerSession session =
new LocalOutgoingServerSession(domain, connection, newSocketReader, streamID);
10 connection.init(session);
11
//
Set the hostname as the address of the session
12
session.setAddress(
new JID(
null, hostname,
null));
13
return session;
14 }
15
else {
16 Log.debug("LocalOutgoingServerSession: OS - Error, SERVER DIALBACK with " + hostname + " failed");
17 }
18 }
19
3.2.2 ServerDialback类 authenticateDomain方法:
View Code
1
public boolean authenticateDomain(OutgoingServerSocketReader socketReader, String domain,
2 String hostname, String id) {
3 String key = AuthFactory.createDigest(id, getSecretkey());
4 Log.debug(
"
ServerDialback: OS - Sent dialback key to host:
" + hostname +
"
id:
" + id +
"
from domain:
" +
5 domain);
6
7 synchronized (socketReader) {
8
//
Send a dialback key to the Receiving Server
9
StringBuilder sb =
new StringBuilder();
10 sb.append(
"
<db:result
");
11 sb.append(
"
from=\"
").append(domain).append(
"
\"
");
12 sb.append(
"
to=\"
").append(hostname).append(
"
\">
");
13 sb.append(key);
14 sb.append(
"
</db:result>
");
15 connection.deliverRawText(sb.toString());
16
17
//
Process the answer from the Receiving Server
18
try {
19 Element doc = socketReader.getElement(RemoteServerManager.getSocketTimeout(),
20 TimeUnit.MILLISECONDS);
21
if (doc ==
null) {
22 Log.debug(
"
ServerDialback: OS - Time out waiting for answer in validation from:
" + hostname +
23
"
id:
" +
24 id +
25
"
for domain:
" +
26 domain);
27
return
false;
28 }
29
else
if (
"
db
".equals(doc.getNamespacePrefix()) &&
"
result
".equals(doc.getName())) {
30 boolean success =
"
valid
".equals(doc.attributeValue(
"
type
"));
31 Log.debug(
"
ServerDialback: OS - Validation
" + (success ?
"
GRANTED
" :
"
FAILED
") +
"
from:
" +
32 hostname +
33
"
id:
" +
34 id +
35
"
for domain:
" +
36 domain);
37
return success;
38 }
39
else {
40 Log.debug(
"
ServerDialback: OS - Unexpected answer in validation from:
" + hostname +
"
id:
" +
41 id +
42
"
for domain:
" +
43 domain +
44
"
answer:
" +
45 doc.asXML());
46
return
false;
47 }
48 }
49
catch (InterruptedException e) {
50 Log.debug(
"
ServerDialback: OS - Validation FAILED from:
" + hostname +
51
"
id:
" +
52 id +
53
"
for domain:
" +
54 domain, e);
55
return
false;
56 }
57 }
58 }
4、加入路由表的serversCache缓存。如 session.addHostname(hostname);
LocalOutgoingServerSession类 addHostname方法:
View Code
1
public
void addHostname(String hostname) {
2 synchronized (hostnames) {
3 hostnames.add(hostname);
4 }
5
//
Add a new route for this new session
6
XMPPServer.getInstance().getRoutingTable().addServerRoute(
new JID(
null, hostname,
null,
true),
this);
7 }
RoutingTableImpl类 addServerRoute(JID route, LocalOutgoingServerSession destination)方法:
View Code
1
public
void addServerRoute(JID route, LocalOutgoingServerSession destination) {
2 String address = route.getDomain();
3 localRoutingTable.addRoute(address, destination);
4 Lock
lock = CacheFactory.getLock(address, serversCache);
5
try {
6
lock.
lock();
7 serversCache.put(address, server.getNodeID().toByteArray());
8 }
9
finally {
10
lock.unlock();
11 }
12 }
5、包再次路由,发往对方。
5.1 OutgoingSessionPromise类 sendPacket(Packet packet)方法:
View Code
1
private
void sendPacket(Packet packet) throws Exception {
2
//
Create a connection to the remote server from the domain where the packet has been sent
3
boolean created;
4
//
Make sure that only one cluster node is creating the outgoing connection
5
//
TODO: Evaluate why removing the oss part causes nasty s2s and lockup issues.
6
Lock
lock = CacheFactory.getLock(domain+
"
oss
", serversCache);
7
try {
8
lock.
lock();
9 created = LocalOutgoingServerSession
10 .authenticateDomain(packet.getFrom().getDomain(), packet.getTo().getDomain());
11 }
finally {
12
lock.unlock();
13 }
14
if (created) {
15
if (!routingTable.hasServerRoute(packet.getTo())) {
16
throw
new Exception(
"
Route created but not found!!!
");
17 }
18
//
A connection to the remote server was created so get the route and send the packet
19
routingTable.routePacket(packet.getTo(), packet,
false);
20 }
21
else {
22
throw
new Exception(
"
Failed to create connection to remote server
");
23 }
24 }
5.2 RoutingTableImpl类 routePacket(JID jid, Packet packet, boolean fromServer)方法:
View Code
1
//
Packet sent to remote server
2
byte[] nodeID = serversCache.get(jid.getDomain());
3
if (nodeID !=
null) {
4
if (server.getNodeID().equals(nodeID)) {
5
//
This is a route to a remote server connected from this node
6
try {
7 localRoutingTable.getRoute(jid.getDomain()).process(packet);
8 routed =
true;
9 }
catch (UnauthorizedException e) {
10 Log.error(e);
11 }
12 }
13
else {
14
//
This is a route to a remote server connected from other node
15
if (remotePacketRouter !=
null) {
16 routed = remotePacketRouter.routePacket(nodeID, jid, packet);
17 }
18 }
19 }
20
else {
21
//
Return a promise of a remote session. This object will queue packets pending
22
//
to be sent to remote servers
23
OutgoingSessionPromise.getInstance().process(packet);
24 routed =
true;
25 }
5.3 通过SocketConnection类的deliver(Packet packet)发出:
View Code
1
public
void deliver(Packet packet)
throws UnauthorizedException, PacketException {
2
if (isClosed()) {
3 backupDeliverer.deliver(packet);
4 }
5
else {
6
boolean errorDelivering =
false;
7
boolean allowedToWrite =
false;
8
try {
9 requestWriting();
10 allowedToWrite =
true;
11 xmlSerializer.write(packet.getElement());
12
if (flashClient) {
13 writer.write('\0');
14 }
15 xmlSerializer.flush();
16 }
17
catch (Exception e) {
18 Log.debug("Error delivering packet" + "\n" +
this.toString(), e);
19 errorDelivering =
true;
20 }
21
finally {
22
if (allowedToWrite) {
23 releaseWriting();
24 }
25 }
26
if (errorDelivering) {
27 close();
28
//
Retry sending the packet again. Most probably if the packet is a
29
//
Message it will be stored offline
30
backupDeliverer.deliver(packet);
31 }
32
else {
33 session.incrementServerPacketCount();
34 }
35 }
36 }
6、对方Openfire服务器在ServerSocketReader类的 packetReceived(Packet packet) 接收,并响应回包。
View Code
1
private
void packetReceived(Packet packet)
throws PacketRejectedException {
2
if (packet.getTo() ==
null || packet.getFrom() ==
null) {
3 Log.debug("Closing IncomingServerSession due to packet with no TO or FROM: " +
4 packet.toXML());
5
//
Send a stream error saying that the packet includes no TO or FROM
6
StreamError error =
new StreamError(StreamError.Condition.improper_addressing);
7 connection.deliverRawText(error.toXML());
8
//
Close the underlying connection
9
connection.close();
10 open =
false;
11
throw
new PacketRejectedException("Packet with no TO or FROM attributes");
12 }
13
else
if (!((LocalIncomingServerSession) session).isValidDomain(packet.getFrom().getDomain())) {
14 Log.debug("Closing IncomingServerSession due to packet with invalid domain: " +
15 packet.toXML());
16
//
Send a stream error saying that the packet includes an invalid FROM
17
StreamError error =
new StreamError(StreamError.Condition.invalid_from);
18 connection.deliverRawText(error.toXML());
19
//
Close the underlying connection
20
connection.close();
21 open =
false;
22
throw
new PacketRejectedException("Packet with no TO or FROM attributes");
23 }
24 }