Openfire S2S 经验分享

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, nulltrue), 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     }
原文地址:https://www.cnblogs.com/huazai8204/p/2295669.html